Skip to content
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

GraalVM Native image build #326

Open
eloyleonardo opened this issue Apr 29, 2024 · 6 comments
Open

GraalVM Native image build #326

eloyleonardo opened this issue Apr 29, 2024 · 6 comments

Comments

@eloyleonardo
Copy link

I'm experiencing difficulties while trying to compile Javet with GraalVM Native Image.

I followed the instructions from issues #107 and #255, setting the -Djavet.lib.loading.path and -Djavet.lib.loading.type variables during the build process. As recommended, I used the absolute path where the dynamic libraries (dylibs) are located. However, the executable fails upon running.

I have created a simple example project to illustrate the issue, available at: https://github.com/eloyleonardo/javetlab. This project uses Gradle. When I run ./gradlew test, everything works as expected. Yet, when I attempt to execute the native command (./gradlew test nativeTest), the project does not succeed and generates the following stack trace:

com.oracle.svm.core.jdk.UnsupportedFeatureError: No classes have been predefined during the image build to load from bytecodes at runtime.
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:121)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.PredefinedClassesSupport.throwNoBytecodeClasses(PredefinedClassesSupport.java:76)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.PredefinedClassesSupport.loadClass(PredefinedClassesSupport.java:130)
        at [email protected]/java.lang.ClassLoader.defineClass(ClassLoader.java:274)
        at com.caoccao.javet.interop.JavetClassLoader.loadClass(JavetClassLoader.java:126)
        at com.caoccao.javet.interop.JavetClassLoader.load(JavetClassLoader.java:99)
        at com.caoccao.javet.interop.V8Host.loadLibrary(V8Host.java:418)
        at com.caoccao.javet.interop.V8Host.<init>(V8Host.java:67)
        at com.caoccao.javet.interop.V8Host.<init>(V8Host.java:43)
        at com.caoccao.javet.interop.V8Host$V8InstanceHolder.<clinit>(V8Host.java:459)
        at com.caoccao.javet.interop.V8Host.getV8Instance(V8Host.java:119)

I have been using Javet in multiples projects and find it fantastic, particularly for the quality of its documentation. Nevertheless, I am encountering difficulties with this specific case.

I would greatly appreciate any guidance or recommendations you could offer.
What steps would you advise me to take next to resolve this complication?

@caoccao
Copy link
Owner

caoccao commented Apr 29, 2024

Please refer to this issue for some ideas.

I think basically you will have to tweak the reflect-config.json or upgrade to the latest Spring Boot.

@eloyleonardo
Copy link
Author

Thank you for your previous response.

Following your advice, I used a RuntimeHintsRegistrar for JavetLibLoader, V8Host, and IV8Module, but the error persists. I've also manually updated the reflect-config.json with these classes:

  • com.caoccao.javet.interop.loader.JavetLibLoader
  • com.caoccao.javet.interop.V8Host
  • com.caoccao.javet.values.reference.IV8Module

Despite these efforts, the issue remains. Could you suggest any further classes to include in reflect-config.json?

Moreover, I've set up an agent in the Gradle tests to generate config files automatically, yet many required classes seem to be missing. Any insights on what else I should consider?

@caoccao
Copy link
Owner

caoccao commented Apr 30, 2024

I think you need to register the following classes.

  • com.caoccao.javet.interop.loader.JavetLibLoader
  • com.caoccao.javet.interop.NodeNative
  • com.caoccao.javet.interop.V8Native

You may ping me at discord if you have any questions.

@eloyleonardo
Copy link
Author

As we discussed on Discord, I have conducted some tests using a native build without Spring, but the results were the same.

I created a repository to showcase my tests without Spring, which can be found here: https://github.com/eloyleonardo/javetsolo . For these tests, I used only 'javac', the 'java' command with an agent, and the 'native-image' command.

Considering your successful experience with a similar build, I am increasingly convinced that I've made a mistake somewhere along the line. However, I'm currently unable to pinpoint exactly where I went wrong.

I must extend my apologies for imposing upon your time once again. My primary intention is merely to provide an update on the issue, incorporating insights from our most recent conversation and the outcomes of my latest attempts.

Once again, thank you for your continued support and patience.

@caoccao
Copy link
Owner

caoccao commented May 2, 2024

I think the problem might be: The jar you built is not a fat jar.

Let's take my sample project JavetShell for an instance.

The gradle build script snippet is as follows.

tasks.jar {
    manifest {
        attributes["Main-Class"] = "${project.group}.shell.MainKt"
    }
    configurations["compileClasspath"].forEach { file: File ->
        from(zipTree(file.absoluteFile))
    }
    duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

The jar file size is ~80MB on Windows including everything, e.g. Javet, netty, vertx, kotlin, jackson, bytebuddy, antlr, ... etc.

image

Assuming latest Visual Studio 2022, GraalVM 21+ are installed, open x64 Native Tools Command Prompt for VS 2022 and we will get a new console open as follows.

**********************************************************************
** Visual Studio 2022 Developer Command Prompt v17.9.3
** Copyright (c) 2022 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

Assuming JavetShell is already built via gradle build, navigate to JavetShell\console and execute the following command.

> where_graalvm_root_is\bin\native-image.cmd -jar build\libs\javet-shell-0.1.0.jar

Warning: The option '-H:ReflectionConfigurationResources=META-INF/native-image/io.netty/netty-transport/reflection-config.json' is experimental and must be enabled via '-H:+UnlockExperimentalVMOptions' in the future.
Warning: Please re-evaluate whether any experimental option is required, and either remove or unlock it. The build output lists all active experimental options, including where they come from and possible alternatives. If you think an experimental option should be considered as stable, please file an issue.
========================================================================================================================
GraalVM Native Image: Generating 'javet-shell-0.1.0' (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...                                                                                   (10.3s @ 0.39GB)
 Java version: 21.0.2+13, vendor version: GraalVM CE 21.0.2+13.1
 Graal compiler: optimization level: 2, target machine: x86-64-v3
 C compiler: cl.exe (microsoft, x64, 19.39.33522)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
 1 user-specific feature(s):
 - com.oracle.svm.thirdparty.gson.GsonFeature
------------------------------------------------------------------------------------------------------------------------
 1 experimental option(s) unlocked:
 - '-H:ReflectionConfigurationResources' (origin(s): 'META-INF\native-image\io.netty\netty-transport\native-image.properties' in 'file:///C:/home/coding/public/JavetShell/console/build/libs/javet-shell-0.1.0.jar')
------------------------------------------------------------------------------------------------------------------------
Build resources:
 - 26.49GB of memory (20.7% of 127.91GB system memory, determined at start)
 - 32 thread(s) (100.0% of 32 available processor(s), determined at start)
May 02, 2024 10:09:03 AM io.netty.handler.ssl.BouncyCastleAlpnSslUtils <clinit>
SEVERE: Unable to initialize BouncyCastleAlpnSslUtils.
java.lang.ClassNotFoundException: org.bouncycastle.jsse.BCSSLEngine
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageClassLoader.loadClass(NativeImageClassLoader.java:652)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Class.java:421)
        at java.base/java.lang.Class.forName(Class.java:412)
        at io.netty.handler.ssl.BouncyCastleAlpnSslUtils.<clinit>(BouncyCastleAlpnSslUtils.java:63)
        at java.base/jdk.internal.misc.Unsafe.ensureClassInitialized0(Native Method)
        at java.base/jdk.internal.misc.Unsafe.ensureClassInitialized(Unsafe.java:1160)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ClassInitializationSupport.ensureClassInitialized(ClassInitializationSupport.java:177)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ProvenSafeClassInitializationSupport.computeInitKindAndMaybeInitializeClass(ProvenSafeClassInitializationSupport.java:348)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ProvenSafeClassInitializationSupport.computeInitKindAndMaybeInitializeClass(ProvenSafeClassInitializationSupport.java:76)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ClassInitializationSupport.maybeInitializeAtBuildTime(ClassInitializationSupport.java:161)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ClassInitializationSupport.maybeInitializeAtBuildTime(ClassInitializationSupport.java:150)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.SVMHost.isInitialized(SVMHost.java:318)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.meta.AnalysisType.isInitialized(AnalysisType.java:911)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.maybeEagerlyInitialize(BytecodeParser.java:4444)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.genInvokeStatic(BytecodeParser.java:1684)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.genInvokeStatic(BytecodeParser.java:1677)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.processBytecode(BytecodeParser.java:5441)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.iterateBytecodesForBlock(BytecodeParser.java:3431)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.phases.SharedGraphBuilderPhase$SharedBytecodeParser.iterateBytecodesForBlock(SharedGraphBuilderPhase.java:743)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.handleBytecodeBlock(BytecodeParser.java:3391)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.processBlock(BytecodeParser.java:3233)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.build(BytecodeParser.java:1137)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.phases.SharedGraphBuilderPhase$SharedBytecodeParser.build(SharedGraphBuilderPhase.java:162)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.buildRootMethod(BytecodeParser.java:1029)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.GraphBuilderPhase$Instance.run(GraphBuilderPhase.java:101)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.phases.SharedGraphBuilderPhase.run(SharedGraphBuilderPhase.java:116)
        at jdk.internal.vm.compiler/org.graalvm.compiler.phases.Phase.run(Phase.java:49)
        at jdk.internal.vm.compiler/org.graalvm.compiler.phases.BasePhase.apply(BasePhase.java:434)
        at jdk.internal.vm.compiler/org.graalvm.compiler.phases.Phase.apply(Phase.java:42)
        at jdk.internal.vm.compiler/org.graalvm.compiler.phases.Phase.apply(Phase.java:38)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.flow.AnalysisParsedGraph.parseBytecode(AnalysisParsedGraph.java:146)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.meta.AnalysisMethod.parseGraph(AnalysisMethod.java:895)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.meta.AnalysisMethod.ensureGraphParsedHelper(AnalysisMethod.java:860)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.meta.AnalysisMethod.ensureGraphParsed(AnalysisMethod.java:843)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.parse(MethodTypeFlowBuilder.java:186)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.apply(MethodTypeFlowBuilder.java:621)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.flow.MethodTypeFlow.createFlowsGraph(MethodTypeFlow.java:167)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.flow.MethodTypeFlow.ensureFlowsGraphCreated(MethodTypeFlow.java:153)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.flow.MethodTypeFlow.getOrCreateMethodFlowsGraphInfo(MethodTypeFlow.java:111)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.typestate.DefaultVirtualInvokeTypeFlow.onObservedUpdate(DefaultVirtualInvokeTypeFlow.java:114)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.flow.TypeFlow.lambda$addObserver$0(TypeFlow.java:475)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.CompletionExecutor.executeCommand(CompletionExecutor.java:187)
        at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.CompletionExecutor.lambda$executeService$0(CompletionExecutor.java:171)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1423)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
        at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)

[2/8] Performing analysis...  [******]                                                                  (15.0s @ 0.76GB)
    9,816 reachable types   (84.4% of   11,637 total)
   14,279 reachable fields  (55.3% of   25,804 total)
   48,509 reachable methods (52.8% of   91,863 total)
    3,207 types,   101 fields, and 2,474 methods registered for reflection
       76 types,    55 fields, and    68 methods registered for JNI access
        5 native libraries: crypt32, ncrypt, psapi, version, winhttp
[3/8] Building universe...                                                                               (3.4s @ 1.09GB)

Warning: Dynamic proxy method java.lang.reflect.Proxy.newProxyInstance invoked at io.netty.handler.ssl.BouncyCastleAlpnSslUtils.setHandshakeApplicationProtocolSelector(BouncyCastleAlpnSslUtils.java:207)
Warning: Aborting stand-alone image build due to dynamic proxy use without configuration.
------------------------------------------------------------------------------------------------------------------------
                        1.5s (5.0% of total time) in 50 GCs | Peak RSS: 1.79GB | CPU load: 8.11
========================================================================================================================
Finished generating 'javet-shell-0.1.0' in 29.1s.
Generating fallback image...
Warning: Image 'javet-shell-0.1.0' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary).

Please disregard the error message from a misconfig of netty, as javet-shell-0.1.0.exe (~14MB) is generated under JavetShell\console. Execute it and the output is as follows.

> javet-shell-0.1.0.exe
Javet Shell v0.1.0 (V8 12.3.219.10)
Please input the script or press Ctrl+C to exit.
Debug port is 9229

V > const a = 'test'
undefined
V > a
test
V >

The CPU and memory usages are quite low, as expected.

image

With the fat jar, there is no need to write any config files. Please let me know if you have any questions.

@eloyleonardo
Copy link
Author

I've been following the provided guide to use JavetShell, and it works perfectly.

To further test, I made modifications to my second example project that does not utilize Spring. This project also successfully compiles using the native-image -jar command.

When i try it with spring, things get a little more complex, I was able create a fat JAR using ./gradlew bootJar. The resulting JAR runs as expected. However, when I attempt to compile this JAR to a native image using the native-image -jar command, I encounter an error. It appears that the native-image utility is expecting a standard JAR file, as discussed in this issue.

This leads me to what appears to be the main problem here.
By testing separately, Javet works correctly in a native environment without Spring. Similarly, Spring can also be compiled to native images without issues.

So, the problem here seems to be the native build Gradle plugin.
If I add it to the project and try ./gradlew nativeCompile, it completes without any errors. However, executing the native binary generated (./build/native/nativeCompile/my-app) results in a runtime error.

java.lang.NullPointerException
	at com.caoccao.javet.interop.JavetClassLoader.loadClass(JavetClassLoader.java:123)
	at com.caoccao.javet.interop.JavetClassLoader.load(JavetClassLoader.java:99)
	at com.caoccao.javet.interop.V8Host.loadLibrary(V8Host.java:418)
	at com.caoccao.javet.interop.V8Host.<init>(V8Host.java:67)
	at com.caoccao.javet.interop.V8Host.<init>(V8Host.java:43)
	at com.caoccao.javet.interop.V8Host$V8InstanceHolder.<clinit>(V8Host.java:459)
	at com.caoccao.javet.interop.V8Host.getV8Instance(V8Host.java:119)
	at com.eloyleonardo.javet.javetlab.JavetlabApplication.main(JavetlabApplication.java:10)
java.lang.ClassNotFoundException: com.caoccao.javet.interop.loader.JavetLibLoader
	at com.caoccao.javet.interop.JavetClassLoader.loadClass(JavetClassLoader.java:132)
	at com.caoccao.javet.interop.JavetClassLoader.load(JavetClassLoader.java:99)
	at com.caoccao.javet.interop.V8Host.loadLibrary(V8Host.java:418)
	at com.caoccao.javet.interop.V8Host.<init>(V8Host.java:67)
	at com.caoccao.javet.interop.V8Host.<init>(V8Host.java:43)
	at com.caoccao.javet.interop.V8Host$V8InstanceHolder.<clinit>(V8Host.java:459)
	at com.caoccao.javet.interop.V8Host.getV8Instance(V8Host.java:119)
	at com.eloyleonardo.javet.javetlab.JavetlabApplication.main(JavetlabApplication.java:10)
Caused by: java.lang.NullPointerException
	at com.caoccao.javet.interop.JavetClassLoader.loadClass(JavetClassLoader.java:123)

I'm uncertain whether the issue lies with my configuration of the Gradle plugin or if there's an inherent problem with it.
I plan to conduct further testing tomorrow to investigate this matter more thoroughly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants