Skip to content

Commit 0782691

Browse files
committed
graalvm
1 parent 35e4d8a commit 0782691

10 files changed

Lines changed: 1203 additions & 10 deletions

File tree

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
"editor.formatOnPaste": false,
66
"editor.formatOnType": false,
77
"editor.formatOnSave": false,
8-
"java.jdt.ls.vmargs": "-Xms512M -Xmx2G -XX:+UseG1GC -XX:+UseStringDeduplication -XX:AdaptiveSizePolicyWeight=90"
8+
"java.jdt.ls.vmargs": "-Xms512M -Xmx4G -XX:+UseG1GC -XX:+UseStringDeduplication -XX:AdaptiveSizePolicyWeight=90"
99
}

common.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ java {
2020
targetCompatibility = JavaVersion.VERSION_1_8
2121
}
2222

23+
apply from: rootProject.file('gradle/native-image-metadata.gradle')
24+
2325
tasks.withType(JavaCompile) { // compile-time options:
2426
//options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings
2527
options.compilerArgs << '-Xlint:unchecked'

gradle/native-image-metadata.gradle

Lines changed: 479 additions & 0 deletions
Large diffs are not rendered by default.

jme3-core/src/main/java/com/jme3/system/Platform.java

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
*/
3232
package com.jme3.system;
3333

34+
import java.nio.file.Files;
35+
import java.nio.file.Path;
36+
import java.nio.file.Paths;
37+
3438
/**
3539
* Enumerate known operating system/architecture pairs.
3640
*/
@@ -76,15 +80,15 @@ public enum Platform {
7680
*/
7781
Android_X86_64(Os.Android, true),
7882

79-
/**
80-
* iOS on ARM
81-
*/
82-
iOS_ARM(Os.iOS, true),
83-
84-
/**
85-
* iOS on x86_64 (simulator)
86-
*/
87-
iOS_X86(Os.iOS, true),
83+
/**
84+
* iOS on ARM
85+
*/
86+
iOS_ARM(Os.iOS, true),
87+
88+
/**
89+
* iOS on x86_64 (simulator)
90+
*/
91+
iOS_X86(Os.iOS, true),
8892
/**
8993
* Generic web platform on unknown architecture
9094
*/
@@ -124,6 +128,8 @@ public enum Os {
124128

125129
private final boolean is64bit;
126130
private final Os os;
131+
private static final boolean NATIVE_IMAGE_RUNTIME = detectNativeImageRuntime();
132+
private static final boolean GRAAL_VM_RUNTIME = detectGraalVmRuntime();
127133

128134
/**
129135
* Test for a 64-bit address space.
@@ -143,6 +149,102 @@ public Os getOs() {
143149
return os;
144150
}
145151

152+
/**
153+
* Test whether this process runs on GraalVM or inside a native-image runtime.
154+
*
155+
* @return true if GraalVM/native-image is detected, otherwise false
156+
*/
157+
public boolean isGraalVM() {
158+
return GRAAL_VM_RUNTIME;
159+
}
160+
161+
/**
162+
* Test whether this process is running as a GraalVM native-image executable.
163+
*
164+
* @return true if running inside a native-image runtime, otherwise false
165+
*/
166+
public boolean isNativeImage() {
167+
return NATIVE_IMAGE_RUNTIME;
168+
}
169+
170+
/**
171+
* Test whether this process is running on GraalVM JRE/JDK (not native-image).
172+
*
173+
* @return true if GraalVM is detected and this is not native-image
174+
*/
175+
public boolean isGraalVmJvm() {
176+
return GRAAL_VM_RUNTIME && !NATIVE_IMAGE_RUNTIME;
177+
}
178+
179+
/**
180+
* Resolve a sibling native library directory next to the current executable.
181+
*
182+
* @param directoryName the sibling directory name, for example {@code libs}
183+
* @return absolute path to the directory, or null if unavailable
184+
*/
185+
public static String resolveNativeImageSiblingDirectory(String directoryName) {
186+
if (!NATIVE_IMAGE_RUNTIME || directoryName == null || directoryName.isEmpty()) {
187+
return null;
188+
}
189+
190+
String executableName;
191+
try {
192+
Class<?> processPropertiesClass = Class.forName("org.graalvm.nativeimage.ProcessProperties", false,
193+
Platform.class.getClassLoader());
194+
Object result = processPropertiesClass.getMethod("getExecutableName").invoke(null);
195+
executableName = result instanceof String ? (String) result : null;
196+
} catch (Throwable ignored) {
197+
return null;
198+
}
199+
200+
if (executableName == null || executableName.isEmpty()) {
201+
return null;
202+
}
203+
204+
try {
205+
Path executablePath = Paths.get(executableName).toAbsolutePath().normalize();
206+
Path parent = executablePath.getParent();
207+
if (parent == null) {
208+
return null;
209+
}
210+
Path sibling = parent.resolve(directoryName).normalize();
211+
if (Files.isDirectory(sibling)) {
212+
return sibling.toString();
213+
}
214+
} catch (Throwable ignored) {
215+
return null;
216+
}
217+
218+
return null;
219+
}
220+
221+
private static boolean detectNativeImageRuntime() {
222+
return System.getProperty("org.graalvm.nativeimage.imagecode") != null;
223+
}
224+
225+
private static boolean detectGraalVmRuntime() {
226+
if (NATIVE_IMAGE_RUNTIME) {
227+
return true;
228+
}
229+
230+
String vmName = System.getProperty("java.vm.name", "").toLowerCase();
231+
if (vmName.contains("graal")) {
232+
return true;
233+
}
234+
235+
String vendor = System.getProperty("java.vendor", "").toLowerCase();
236+
if (vendor.contains("graal")) {
237+
return true;
238+
}
239+
240+
try {
241+
Class.forName("org.graalvm.polyglot.Context", false, Platform.class.getClassLoader());
242+
return true;
243+
} catch (ClassNotFoundException ignored) {
244+
return false;
245+
}
246+
}
247+
146248
private Platform(Os os, boolean is64bit) {
147249
this.os = os;
148250
this.is64bit = is64bit;

jme3-examples/build.gradle

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,96 @@
1+
import org.gradle.jvm.toolchain.JavaLanguageVersion
2+
import org.gradle.jvm.toolchain.JvmVendorSpec
3+
4+
import groovy.json.JsonOutput
5+
import java.nio.file.Files
6+
7+
plugins {
8+
id 'org.graalvm.buildtools.native' version '0.10.6'
9+
}
10+
111
ext.mainClassName = 'jme3test.TestChooser'
12+
ext.nativeMainClassName = 'jme3test.TestChooserCli'
13+
ext.jmeNativeMetadataAdditionalResourceGlobs = []
14+
15+
def generatedTestChooserResourcesDir = layout.buildDirectory.dir('generated/testchooser/resources')
16+
def testChooserClassListFile = generatedTestChooserResourcesDir.map { it.file('jme3test/test-classes.txt') }
17+
def testChooserReflectionConfigFile = layout.buildDirectory.file('generated/testchooser/reflect-config.json')
18+
def nativeImageBuildOutputJsonFile = layout.buildDirectory.file('reports/native-image/jme3-testchooser-build-output.json')
19+
def testChooserReachabilityMetadataFile = generatedTestChooserResourcesDir.map {
20+
it.file('META-INF/native-image/org.jmonkeyengine/jme3-examples-testchooser/reachability-metadata.json')
21+
}
22+
23+
tasks.register('generateTestChooserClassList') {
24+
group = 'build'
25+
description = 'Generates a resource list of jme3test classes for TestChooserCli fallback discovery.'
26+
dependsOn 'compileJava'
27+
outputs.files(testChooserClassListFile, testChooserReachabilityMetadataFile, testChooserReflectionConfigFile)
28+
29+
doLast {
30+
Set<String> allJme3TestClassNames = new TreeSet<String>()
31+
Set<String> launcherClassNames = new TreeSet<String>()
32+
sourceSets.main.output.classesDirs.files.findAll { it.exists() }.each { classesDir ->
33+
fileTree(classesDir).matching {
34+
include 'jme3test/**/*.class'
35+
exclude '**/*$*'
36+
exclude '**/module-info.class'
37+
exclude '**/package-info.class'
38+
exclude '**/TestChooser.class'
39+
exclude '**/TestChooserCli.class'
40+
}.files.each { classFile ->
41+
String relativePath = classesDir.toPath().relativize(classFile.toPath()).toString().replace(File.separatorChar, (char) '/')
42+
if (relativePath.endsWith('.class')) {
43+
String className = relativePath.substring(0, relativePath.length() - '.class'.length()).replace('/', '.')
44+
allJme3TestClassNames.add(className)
45+
if (relativePath.contains('Test')) {
46+
launcherClassNames.add(className)
47+
}
48+
}
49+
}
50+
}
51+
52+
File classListFile = testChooserClassListFile.get().asFile
53+
classListFile.parentFile.mkdirs()
54+
classListFile.text = launcherClassNames.isEmpty()
55+
? ''
56+
: launcherClassNames.join(System.lineSeparator()) + System.lineSeparator()
57+
58+
List<Map<String, Object>> reflectionEntries = allJme3TestClassNames.collect { className ->
59+
[
60+
type : className,
61+
allDeclaredConstructors: true,
62+
allDeclaredMethods : true,
63+
allPublicMethods : true
64+
]
65+
}
66+
Map<String, Object> metadata = [
67+
reflection: reflectionEntries,
68+
resources : []
69+
]
70+
71+
File reachabilityFile = testChooserReachabilityMetadataFile.get().asFile
72+
reachabilityFile.parentFile.mkdirs()
73+
reachabilityFile.text = JsonOutput.prettyPrint(JsonOutput.toJson(metadata)) + System.lineSeparator()
74+
75+
List<Map<String, Object>> reflectionConfigEntries = allJme3TestClassNames.collect { className ->
76+
[
77+
name : className,
78+
allDeclaredConstructors: true,
79+
allDeclaredMethods : true,
80+
allPublicMethods : true
81+
]
82+
}
83+
File reflectionConfigFile = testChooserReflectionConfigFile.get().asFile
84+
reflectionConfigFile.parentFile.mkdirs()
85+
reflectionConfigFile.text = JsonOutput.prettyPrint(JsonOutput.toJson(reflectionConfigEntries)) + System.lineSeparator()
86+
}
87+
}
88+
89+
sourceSets.main.resources.srcDir(generatedTestChooserResourcesDir)
90+
91+
tasks.named('processResources') {
92+
dependsOn 'generateTestChooserClassList'
93+
}
294

395
def androidProject = project(':jme3-android')
496
def androidNativeProject = project(':jme3-android-native')
@@ -37,6 +129,74 @@ task runExamples(dependsOn: 'build', type: JavaExec) {
37129
}
38130
}
39131

132+
def graalLauncher = javaToolchains.launcherFor {
133+
languageVersion = JavaLanguageVersion.of(21)
134+
vendor = JvmVendorSpec.GRAAL_VM
135+
nativeImageCapable = true
136+
}
137+
138+
graalvmNative {
139+
binaries {
140+
named('main') {
141+
imageName = 'jme3-testchooser'
142+
mainClass = nativeMainClassName
143+
sharedLibrary = false
144+
javaLauncher = graalLauncher
145+
project.ext.jmeApplyDefaultNativeImageResourceSettings(delegate)
146+
buildArgs.add("-H:ReflectionConfigurationFiles=${testChooserReflectionConfigFile.get().asFile.absolutePath}")
147+
buildArgs.add('-H:+BuildOutputBreakdowns')
148+
buildArgs.add("-H:BuildOutputJSONFile=${nativeImageBuildOutputJsonFile.get().asFile.absolutePath}")
149+
}
150+
}
151+
}
152+
153+
tasks.named('nativeCompile') {
154+
dependsOn 'processResources'
155+
156+
doFirst {
157+
// Gradle's Copy task cannot preserve symlinks in some provisioned toolchains.
158+
// Recreate likely broken links in the GraalVM toolchain before native-image runs.
159+
File graalvmHomeDir = System.getenv('GRAALVM_HOME') ? file(System.getenv('GRAALVM_HOME')) : null
160+
def nativeImageLauncher = javaToolchains.launcherFor {
161+
languageVersion = JavaLanguageVersion.of(21)
162+
vendor = JvmVendorSpec.GRAAL_VM
163+
nativeImageCapable = true
164+
}
165+
166+
if (delegate.hasProperty('options') && delegate.options.hasProperty('javaLauncher')) {
167+
delegate.options.javaLauncher.set(nativeImageLauncher)
168+
}
169+
170+
File toolchainDir = graalvmHomeDir
171+
if (toolchainDir == null) {
172+
File executableParent = nativeImageLauncher.get().executablePath.asFile.parentFile
173+
toolchainDir = executableParent.name == 'bin' ? executableParent.parentFile : executableParent
174+
}
175+
176+
def toolchainFiles = project.fileTree(toolchainDir).files.findAll { it.isFile() }
177+
def emptyFiles = toolchainFiles.findAll { it.length() == 0L }
178+
Set<File> emptyFileSet = emptyFiles as Set<File>
179+
180+
toolchainFiles.groupBy { it.name }.each { ignoredName, sameNamedFiles ->
181+
List<File> nonEmptyCandidates = sameNamedFiles.findAll { it.length() > 0L }
182+
List<File> emptyCandidates = sameNamedFiles.findAll { emptyFileSet.contains(it) }
183+
if (!nonEmptyCandidates.isEmpty() && !emptyCandidates.isEmpty()) {
184+
File target = nonEmptyCandidates.first()
185+
emptyCandidates.each { File link ->
186+
if (link != target) {
187+
logger.quiet("Fixing up '${link}' to link to '${target}'.")
188+
if (link.delete()) {
189+
Files.createSymbolicLink(link.toPath(), target.toPath())
190+
} else {
191+
logger.warn("Unable to delete '${link}'.")
192+
}
193+
}
194+
}
195+
}
196+
}
197+
}
198+
}
199+
40200
dependencies {
41201
implementation project(':jme3-core')
42202
implementation project(':jme3-desktop')

jme3-examples/src/main/java/jme3test/TestChooser.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.jme3.app.LegacyApplication;
3636
import com.jme3.app.SimpleApplication;
3737
import com.jme3.system.JmeContext;
38+
import com.jme3.system.JmeSystem;
3839
import java.awt.BorderLayout;
3940
import java.awt.Dimension;
4041
import java.awt.FlowLayout;
@@ -497,6 +498,11 @@ private void center() {
497498
* command line parameters
498499
*/
499500
public static void main(final String[] args) {
501+
if (JmeSystem.getPlatform().isGraalVM()) {
502+
TestChooserCli.main(args);
503+
return;
504+
}
505+
500506
try {
501507
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
502508
} catch (Exception e) {}

0 commit comments

Comments
 (0)