libgdx / gdx-jnigen

jnigen is a small library that can be used with or without libGDX which allows C/C++ code to be written inline with Java source code.
Apache License 2.0
59 stars 26 forks source link

fix: Ensure to load resource with global class loader #61

Open gudzpoz opened 4 months ago

gudzpoz commented 4 months ago

Description

When gdx-jnigen-loader is put on the module path (to be loaded as an automatically named module in JPMS), it cannot load any libraries in a separate JAR anymore. This patch contains a simple fix to hopefully make things work when gdx-jnigen-loader is used as a named module.

Explanation

After Java 9, all classes are put within some modules. There are three kinds of modules:

  1. Unnamed modules: where classes from class path are put.
  2. Automatic modules: where classes from module path are put when the JAR doesn't explicitly declare a module.
  3. Explicitly declared modules.

With JPMS, Class#getResource by default loads resources only from the current module, making SharedLibraryLoader malfunction, whose purpose is to load libraries from other JARs (modules). In contrast, ClassLoader#getResource tries to find resources from all loaded modules, which should be used instead to allow using jnigen as an automatic module.

Reproducing Steps

Preparations

$ # The jnigen loader JAR
$ wget https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-jnigen-loader/2.5.1/gdx-jnigen-loader-2.5.1.jar
$ # A JAR containing binaries built with jnigen
$ wget https://repo1.maven.org/maven2/party/iroiro/luajava/lua54-platform/3.5.0/lua54-platform-3.5.0-natives-desktop.jar

A Working Example

$ jshell --class-path lua54-platform-3.5.0-natives-desktop.jar:gdx-jnigen-loader-2.5.1.jar
jshell> import com.badlogic.gdx.utils.SharedLibraryLoader;
jshell> var loader = new SharedLibraryLoader();
jshell> loader.load("lua54");
jshell> // Library loaded with no exception thrown

A Failing One

$ jshell --module-path lua54-platform-3.5.0-natives-desktop.jar:gdx-jnigen-loader-2.5.1.jar --add-module gdx.jnigen.loader
jshell> import com.badlogic.gdx.utils.SharedLibraryLoader;
jshell> var loader = new SharedLibraryLoader();
jshell> loader.load("lua54");
|  Exception com.badlogic.gdx.utils.SharedLibraryLoadRuntimeException: Couldn't load shared library 'liblua5464.so' for target: Linux, x86, 64-bit
|        at SharedLibraryLoader.load (SharedLibraryLoader.java:174)
|        at (#4:1)
|  Caused by: com.badlogic.gdx.utils.SharedLibraryLoadRuntimeException: Unable to read file for extraction: liblua5464.so
|        at SharedLibraryLoader.readFile (SharedLibraryLoader.java:183)
|        at SharedLibraryLoader.loadFile (SharedLibraryLoader.java:339)
|        at SharedLibraryLoader.load (SharedLibraryLoader.java:170)
|        ...
Berstanio commented 4 months ago

Hi, thank you for the detailed description! When reading up on the docs of ClassLoader#getResource, I noticed the following:

Resources in named modules are subject to the encapsulation rules specified by Module.getResourceAsStream. Additionally, and except for the special case where the resource has a name ending with ".class", this method will only find resources in packages of named modules when the package is opened unconditionally (even if the caller of this method is in the same module as the resource).

(https://download.java.net/java/early_access/panama/docs/api/java.base/java/lang/ClassLoader.html)

I'm not really familiar with java modules, so I'm not sure about the implication of this. But I think we should be unaffected, since all resources are in the root directory?

gudzpoz commented 4 months ago

I couldn't find any info on resources in root directories of modules, but they indeed seem opened when testing with the following setup.

+- module-info.java  -> exports only b.c
+- b/
|  +- c/
|  |  +- C.class
|  |  \- C.txt       -> accessible
|  |
|  +- B.class
|  \- B.txt          -> inaccessible
|
\- A.txt             -> accessible!!

But I don't think it will be a problem anyway. The JAR generated by the Ant pack-native task does not contain module-info.java, which means the JAR for binaries will never be an explicitly declared module, unless the user manually rebundles. (The other two categories, i.e., unnamed modules and automatic modules, simply open every package.)