Skip to content

Commit 72f7aae

Browse files
authored
GraalVM support (#2740)
1 parent b5ba9c4 commit 72f7aae

17 files changed

Lines changed: 2231 additions & 92 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
**/.vscode/**
66
**/out/
77
/.gradle/
8+
**/.gradle/
89
/.nb-gradle/
910
/.idea/
1011
/dist/

build.gradle

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ buildscript {
1212
}
1313
}
1414

15+
plugins {
16+
id 'org.jmonkeyengine.nativeimage' apply false
17+
}
18+
19+
import org.gradle.api.file.RelativePath
20+
1521
// Set the license for IDEs that understand this
1622
ext.license = file("$rootDir/source-file-header-template.txt")
1723

@@ -54,6 +60,25 @@ subprojects {
5460
}
5561
}
5662

63+
def nativeImagePluginBuild = gradle.includedBuild('jme3-nativeimage-plugin')
64+
def nativeImagePluginPublishTasks = [
65+
publish : ':publish',
66+
publishToMavenLocal : ':publishToMavenLocal',
67+
install : ':install',
68+
publishAllPublicationsToCentralRepository : ':publishAllPublicationsToCentralRepository',
69+
publishAllPublicationsToSNAPSHOTRepository : ':publishAllPublicationsToSNAPSHOTRepository',
70+
publishAllPublicationsToDistRepository : ':publishAllPublicationsToDistRepository'
71+
]
72+
73+
subprojects {
74+
tasks.configureEach { task ->
75+
String includedTaskPath = nativeImagePluginPublishTasks[task.name]
76+
if (includedTaskPath != null) {
77+
task.dependsOn nativeImagePluginBuild.task(includedTaskPath)
78+
}
79+
}
80+
}
81+
5782
tasks.register('run') {
5883
dependsOn ':jme3-examples:run'
5984
description = 'Run the jME3 examples'

common.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ java {
2424
}
2525
}
2626

27+
apply plugin: 'org.jmonkeyengine.nativeimage'
28+
2729
tasks.withType(JavaCompile) { // compile-time options:
2830
//options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings
2931
options.compilerArgs << '-Xlint:unchecked'

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ public enum Os {
221221

222222
private final boolean is64bit;
223223
private final Os os;
224+
private static final boolean NATIVE_IMAGE_RUNTIME = detectNativeImageRuntime();
224225

225226
/**
226227
* Test for a 64-bit address space.
@@ -240,6 +241,19 @@ public Os getOs() {
240241
return os;
241242
}
242243

244+
/**
245+
* Test whether this process is running as a GraalVM native-image executable.
246+
*
247+
* @return true if running inside a native-image runtime, otherwise false
248+
*/
249+
public boolean isGraalVMNativeImage() {
250+
return NATIVE_IMAGE_RUNTIME;
251+
}
252+
253+
private static boolean detectNativeImageRuntime() {
254+
return System.getProperty("org.graalvm.nativeimage.imagecode") != null;
255+
}
256+
243257
private Platform(Os os, boolean is64bit) {
244258
this.os = os;
245259
this.is64bit = is64bit;

jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java

Lines changed: 178 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,25 @@
7070
*
7171
* @author Kirill Vainer
7272
*/
73-
public final class NativeLibraryLoader {
74-
75-
private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName());
76-
private static File extractionFolderOverride = null;
77-
private static File extractionFolder = null;
78-
79-
private static final HashMap<NativeLibrary.Key, NativeLibrary> nativeLibraryMap = new HashMap<>();
73+
public final class NativeLibraryLoader {
74+
/**
75+
* System property containing the filesystem directory where native libraries
76+
* should be extracted to or loaded from.
77+
*/
78+
public static final String CUSTOM_EXTRACTION_FOLDER_PROPERTY = "com.jme3.NativeLibraryExtractionFolder";
79+
80+
/**
81+
* System property controlling whether native libraries should be extracted
82+
* from the classpath before loading. Defaults to {@code true}.
83+
*/
84+
public static final String EXTRACT_NATIVE_LIBRARIES_PROPERTY = "com.jme3.ExtractNativeLibraries";
85+
86+
private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName());
87+
private static File extractionFolderOverride = null;
88+
private static File extractionFolder = null;
89+
private static Boolean extractNativeLibrariesOverride = null;
90+
91+
private static final HashMap<NativeLibrary.Key, NativeLibrary> nativeLibraryMap = new HashMap<>();
8092

8193
static {
8294
NativeLibraries.registerDefaultLibraries();
@@ -174,18 +186,75 @@ public static boolean isUsingNativeBullet() {
174186
*
175187
* @param path Path where to extract native libraries.
176188
*/
177-
public static void setCustomExtractionFolder(String path) {
178-
extractionFolderOverride = new File(path).getAbsoluteFile();
179-
}
189+
public static void setCustomExtractionFolder(String path) {
190+
extractionFolderOverride = path == null ? null : new File(path).getAbsoluteFile();
191+
}
192+
193+
/**
194+
* Returns the configured custom extraction folder.
195+
*
196+
* @return the programmatic override if set, otherwise the
197+
* {@link #CUSTOM_EXTRACTION_FOLDER_PROPERTY} system property
198+
*/
199+
public static File getCustomExtractionFolder() {
200+
if (extractionFolderOverride != null) {
201+
return extractionFolderOverride;
202+
}
203+
204+
String extractionFolderProperty = System.getProperty(CUSTOM_EXTRACTION_FOLDER_PROPERTY);
205+
if (extractionFolderProperty != null && !extractionFolderProperty.trim().isEmpty()) {
206+
return new File(extractionFolderProperty).getAbsoluteFile();
207+
}
208+
209+
return null;
210+
}
211+
212+
/**
213+
* Specify whether native libraries should be extracted from the classpath
214+
* before loading. Set to {@code true} to preserve the default behavior.
215+
*
216+
* @param extractNativeLibraries true to extract classpath natives, false to
217+
* load existing files from the extraction folder
218+
*/
219+
public static void setExtractNativeLibraries(boolean extractNativeLibraries) {
220+
extractNativeLibrariesOverride = extractNativeLibraries;
221+
}
222+
223+
/**
224+
* Clears the programmatic extraction flag override.
225+
*/
226+
public static void clearExtractNativeLibrariesOverride() {
227+
extractNativeLibrariesOverride = null;
228+
}
229+
230+
/**
231+
* Returns whether native libraries should be extracted before loading.
232+
*
233+
* @return the programmatic override if set, otherwise the
234+
* {@link #EXTRACT_NATIVE_LIBRARIES_PROPERTY} system property,
235+
* defaulting to true
236+
*/
237+
public static boolean isExtractNativeLibraries() {
238+
if (extractNativeLibrariesOverride != null) {
239+
return extractNativeLibrariesOverride;
240+
}
241+
242+
String extractNativeLibrariesProperty = System.getProperty(EXTRACT_NATIVE_LIBRARIES_PROPERTY);
243+
return extractNativeLibrariesProperty == null
244+
|| extractNativeLibrariesProperty.trim().isEmpty()
245+
|| Boolean.parseBoolean(extractNativeLibrariesProperty);
246+
}
180247

181248
/**
182249
* Returns the folder where native libraries will be extracted.
183250
* This is automatically determined at run-time based on the
184-
* following criteria:<br>
185-
* <ul>
186-
* <li>If a {@link #setCustomExtractionFolder(java.lang.String) custom
187-
* extraction folder} has been specified, it is returned.
188-
* <li>If the user can write to "java.io.tmpdir" folder, then it
251+
* following criteria:<br>
252+
* <ul>
253+
* <li>If a {@link #setCustomExtractionFolder(java.lang.String) custom
254+
* extraction folder} has been specified, it is returned.</li>
255+
* <li>If the {@link #CUSTOM_EXTRACTION_FOLDER_PROPERTY} system property
256+
* has been specified, it is returned.</li>
257+
* <li>If the user can write to "java.io.tmpdir" folder, then it
189258
* is used.</li>
190259
* <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder}
191260
* is used, to prevent collisions, a special subfolder is used
@@ -196,10 +265,11 @@ public static void setCustomExtractionFolder(String path) {
196265
*
197266
* @return Path where natives will be extracted to.
198267
*/
199-
public static File getExtractionFolder() {
200-
if (extractionFolderOverride != null) {
201-
return extractionFolderOverride;
202-
}
268+
public static File getExtractionFolder() {
269+
File customExtractionFolder = getCustomExtractionFolder();
270+
if (customExtractionFolder != null) {
271+
return customExtractionFolder;
272+
}
203273
if (extractionFolder == null) {
204274
File userTempDir = new File(System.getProperty("java.io.tmpdir"));
205275
if (!userTempDir.canWrite()) {
@@ -480,79 +550,95 @@ public static String loadNativeLibrary(String name, boolean isRequired) {
480550
return null;
481551
}
482552

483-
URL url = Resources.getResource(pathInJar);
484-
485-
if (url == null) {
486-
if (isRequired) {
487-
throw new UnsatisfiedLinkError(
488-
"The required native library '" + library.getName() + "'"
489-
+ " was not found in the classpath via '" + pathInJar);
490-
} else if (logger.isLoggable(Level.FINE)) {
491-
logger.log(Level.FINE, "The optional native library ''{0}''" +
492-
" was not found in the classpath via ''{1}''.",
493-
new Object[]{library.getName(), pathInJar});
494-
}
495-
return null;
496-
}
497-
498-
// The library has been found and is ready to be extracted.
499-
// Determine what filename it should be extracted as.
500-
String loadedAsFileName;
501-
if (library.getExtractedAsName() != null) {
502-
loadedAsFileName = library.getExtractedAsName();
503-
} else {
504-
// Just use the original filename as it is in the JAR.
505-
loadedAsFileName = Paths.get(pathInJar).getFileName().toString();
506-
}
507-
508-
File extractionDirectory = getExtractionFolder();
509-
URLConnection conn;
510-
511-
try {
512-
conn = url.openConnection();
513-
} catch (IOException ex) {
514-
// Maybe put more detail here? Not sure.
515-
throw new UncheckedIOException("Failed to open file: '" + url +
516-
"'. Error: " + ex, ex);
517-
}
518-
519-
File targetFile = new File(extractionDirectory, loadedAsFileName);
520-
try (InputStream in = conn.getInputStream()) {
521-
if (isExtractingRequired(conn, targetFile)) {
522-
Files.copy(in, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
523-
524-
// NOTE: On OSes that support "Date Created" property,
525-
// this will cause the last modified date to be lower than
526-
// date created which makes no sense
527-
targetFile.setLastModified(conn.getLastModified());
528-
529-
if (logger.isLoggable(Level.FINE)) {
530-
logger.log(Level.FINE, "Extracted native library from ''{0}'' into ''{1}''. ",
531-
new Object[]{url, targetFile});
532-
}
533-
} else {
534-
if (logger.isLoggable(Level.FINE)) {
535-
logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.",
536-
loadedAsFileName);
537-
}
538-
}
539-
540-
library.getLoadFunction().accept(targetFile.getAbsolutePath());
541-
542-
if (logger.isLoggable(Level.FINE)) {
543-
logger.log(Level.FINE, "Loaded native library {0}.", library.getName());
544-
}
545-
546-
return targetFile.getAbsolutePath();
547-
} catch (IOException ex) {
548-
/*if (ex.getMessage().contains("used by another process")) {
549-
return;
550-
}*/
551-
552-
throw new UncheckedIOException("Failed to extract native library to: "
553-
+ targetFile, ex);
554-
}
555-
}
553+
String loadedAsFileName = getLoadedAsFileName(library, pathInJar);
554+
File extractionDirectory = getExtractionFolder();
555+
File targetFile = new File(extractionDirectory, loadedAsFileName);
556+
557+
if (isExtractNativeLibraries()) {
558+
URL url = Resources.getResource(pathInJar);
559+
560+
if (url == null) {
561+
if (isRequired) {
562+
throw new UnsatisfiedLinkError(
563+
"The required native library '" + library.getName() + "'"
564+
+ " was not found in the classpath via '" + pathInJar);
565+
} else if (logger.isLoggable(Level.FINE)) {
566+
logger.log(Level.FINE, "The optional native library ''{0}''" +
567+
" was not found in the classpath via ''{1}''.",
568+
new Object[]{library.getName(), pathInJar});
569+
}
570+
return null;
571+
}
572+
573+
// The library has been found and is ready to be extracted.
574+
URLConnection conn;
575+
576+
try {
577+
conn = url.openConnection();
578+
} catch (IOException ex) {
579+
// Maybe put more detail here? Not sure.
580+
throw new UncheckedIOException("Failed to open file: '" + url +
581+
"'. Error: " + ex, ex);
582+
}
583+
584+
try (InputStream in = conn.getInputStream()) {
585+
if (isExtractingRequired(conn, targetFile)) {
586+
Files.copy(in, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
587+
588+
// NOTE: On OSes that support "Date Created" property,
589+
// this will cause the last modified date to be lower than
590+
// date created which makes no sense
591+
targetFile.setLastModified(conn.getLastModified());
592+
593+
if (logger.isLoggable(Level.FINE)) {
594+
logger.log(Level.FINE, "Extracted native library from ''{0}'' into ''{1}''. ",
595+
new Object[]{url, targetFile});
596+
}
597+
} else {
598+
if (logger.isLoggable(Level.FINE)) {
599+
logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.",
600+
loadedAsFileName);
601+
}
602+
}
603+
} catch (IOException ex) {
604+
/*if (ex.getMessage().contains("used by another process")) {
605+
return;
606+
}*/
607+
608+
throw new UncheckedIOException("Failed to extract native library to: "
609+
+ targetFile, ex);
610+
}
611+
} else if (!targetFile.isFile()) {
612+
if (isRequired) {
613+
throw new UnsatisfiedLinkError(
614+
"The required native library '" + library.getName() + "'"
615+
+ " was not found at '" + targetFile
616+
+ "' and native library extraction is disabled");
617+
} else if (logger.isLoggable(Level.FINE)) {
618+
logger.log(Level.FINE, "The optional native library ''{0}''" +
619+
" was not found at ''{1}'' and native library extraction is disabled.",
620+
new Object[]{library.getName(), targetFile});
621+
}
622+
return null;
623+
}
624+
625+
library.getLoadFunction().accept(targetFile.getAbsolutePath());
626+
627+
if (logger.isLoggable(Level.FINE)) {
628+
logger.log(Level.FINE, "Loaded native library {0}.", library.getName());
629+
}
630+
631+
return targetFile.getAbsolutePath();
632+
}
633+
634+
private static String getLoadedAsFileName(NativeLibrary library, String pathInJar) {
635+
if (library.getExtractedAsName() != null) {
636+
return library.getExtractedAsName();
637+
}
638+
639+
// Just use the original filename as it is in the JAR.
640+
return Paths.get(pathInJar).getFileName().toString();
641+
}
556642

557643
/**
558644
* Checks if library extraction is required by comparing source and target

0 commit comments

Comments
 (0)