fusesource / jansi

Jansi is a small java library that allows you to use ANSI escape sequences to format your console output which works even on windows.
http://fusesource.github.io/jansi/
Apache License 2.0
1.11k stars 140 forks source link

Trouble using Jansi with GraalVM native-image on MacOS #199

Closed greenatatlassian closed 2 years ago

greenatatlassian commented 3 years ago

Looks like there was already some work done to address issue #162, but that doesn't seem to quite do everything that was required in my case to get things working with Jansi in a GraalVM native image.

The problem was that native-image build was running the code that finds and links the necessary native library image at build time, so the library was not linked at run time, resulting in an UnsatisfiedLinkError error at runtime:

Exception in thread "main" java.lang.UnsatisfiedLinkError: org.fusesource.jansi.internal.CLibrary.isatty(I)I [symbol: Java_org_fusesource_jansi_internal_CLibrary_isatty or Java_org_fusesource_jansi_internal_CLibrary_isatty__I]
    at com.oracle.svm.jni.access.JNINativeLinkage.getOrFindEntryPoint(JNINativeLinkage.java:153)
    at com.oracle.svm.jni.JNIGeneratedMethodSupport.nativeCallAddress(JNIGeneratedMethodSupport.java:57)
    at org.fusesource.jansi.internal.CLibrary.isatty(CLibrary.java)
        ...

I was able to overcome this by telling the native image that the relevant classes must be initialised at runtime rather than at build time. I achieved this by adding the following class to my codebase:

@AutomaticFeature
public class JansiFeature implements Feature {

    JansiFeature() { /* empty constructor required for Feature operation */ }

    @Override
    public void afterRegistration(AfterRegistrationAccess access) {
        RuntimeClassInitialization.initializeAtRunTime("org.fusesource.jansi.internal");
    }

    @Override
    public void beforeAnalysis(BeforeAnalysisAccess access) {
        JNIRuntimeAccess.register(CLibrary.class);
        JNIRuntimeAccess.register(CLibrary.class.getDeclaredFields());
    }
}

It may be possible to add the necessary configuration (either this file, or appropriate .json config files) to the Jansi library itself so that it will work correctly "out of the box" with native-image builds on GraalVM. If not, perhaps this will at least be useful to someone else who encounters the same problem.

gnodet commented 3 years ago

Do you think you could provide a PR that would provide the needed json files ?

greenatatlassian commented 3 years ago

Might take me a little while, but I'll try to put one together for you.

williamwebb commented 2 years ago

Spent some time trying to get this working with graalvm to no success.

I tested with jansi: 2.3.4 graalvm-ce: 21.2.0 native image executed against: scratch, gcr.io/distroless/static

For reference, when I ran the graalvm native-image-agent against a jar using jansi I got the following ouptput.

resource-config.json

{
  "resources":{
    "includes":[
      {"pattern":"\\QMETA-INF/maven/org.fusesource.jansi/jansi/pom.properties\\E"},
      {"pattern":"\\Qorg/fusesource/jansi/internal/native/Linux/x86_64/libjansi.so\\E"}
    ]},
  "bundles":[]
}

jni-config.json

[
  {
    "name":"java.lang.ClassLoader",
    "methods":[
      {"name":"getPlatformClassLoader","parameterTypes":[] },
      {"name":"loadClass","parameterTypes":["java.lang.String"] }
    ]
  },
  {
    "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"
  },
  {
    "name":"org.fusesource.jansi.internal.CLibrary",
    "fields":[
      {"name":"HAVE_ISATTY"},
      {"name":"HAVE_TTYNAME"},
      {"name":"TCSADRAIN"},
      {"name":"TCSAFLUSH"},
      {"name":"TCSANOW"},
      {"name":"TIOCGETD"},
      {"name":"TIOCGWINSZ"},
      {"name":"TIOCSETD"},
      {"name":"TIOCSWINSZ"}
    ]
  },
  {
    "name":"org.graalvm.nativebridge.jni.JNIExceptionWrapperEntryPoints",
    "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]
  }
]

When running a graalvm native-image using these configs jansi does not work, with the following error printed.

Failed to load native library:jansi-2.3.4-65da82f66e242fcc-libjansi.so. osinfo: Linux/x86_64
java.lang.UnsatisfiedLinkError: Can't load library: /tmp/jansi-2.3.4-65da82f66e242fcc-libjansi.so
williamwebb commented 2 years ago

Further information 1.18 works with no error message 2.0 <-> 2.0.1 works, with error message

gnodet commented 2 years ago

With https://github.com/fusesource/jansi/commit/2cf446182c823a4c110411b765a1f0367eb8a913, I've been able to compile jansi jar to native using GraalVM CE 21.1.0 and run it successfully on OSX. I haven't had any needs for the delayed initialisation. I'll give it a try on the latest GraalVM to be sure.

xtaixe commented 1 year ago

@gnodet FYI I'm hitting this with Jansi 2.4.0 and GraalVM 22.2.0 on an ARM Mac:

Jansi 2.4.0

library.jansi.path=
library.jansi.version=
Jansi native library loaded from /var/folders/qd/msrxtq5j1n9fg4bbsw4xgsbh0000gn/T/jansi-2.4.0-f98251e4f71e7e4-libjansi.jnilib
   which was auto-extracted from jar:file:/Users/xtaixe/dev/cli/app/target/native-image-source-jar/lib/org.fusesource.jansi.jansi-2.4.0.jar!/org/fusesource/jansi/internal/native/Mac/arm64/libjansi.jnilib

os.name= Mac OS X, os.version= 12.6, os.arch= aarch64
file.encoding= UTF-8
java.version= 17.0.4, java.vendor= Oracle Corporation, java.home= null

jansi.graceful=
jansi.mode=
jansi.out.mode=
jansi.err.mode=
jansi.colors=
jansi.out.colors=
jansi.err.colors=
jansi.passthrough= false
jansi.strip= false
jansi.force= false
jansi.noreset= false
org.fusesource.jansi.Ansi.disable= false

IS_WINDOWS: false

Exception in thread "main" java.lang.UnsatisfiedLinkError: org.fusesource.jansi.internal.CLibrary.isatty(I)I [symbol: Java_org_fusesource_jansi_internal_CLibrary_isatty or Java_org_fusesource_jansi_internal_CLibrary_isatty__I]
    at com.oracle.svm.jni.access.JNINativeLinkage.getOrFindEntryPoint(JNINativeLinkage.java:154)
    at com.oracle.svm.jni.JNIGeneratedMethodSupport.nativeCallAddress(JNIGeneratedMethodSupport.java:52)
    at org.fusesource.jansi.internal.CLibrary.isatty(CLibrary.java)

For now I haven't been able to apply the fix above and if I use --initialize-at-run-time=org.fusesource.jansi.internal.CLibrary I get Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.fusesource.jansi.internal.CLibrary.

I'll report if I find out more.

xtaixe commented 1 year ago

Ah, using --initialize-at-run-time=org.fusesource.jansi.internal works.

According to https://medium.com/graalvm/updates-on-class-initialization-in-graalvm-native-image-generation-c61faca461f7, libraries can be shipped with these these command line options in native-image.properties files.

xtaixe commented 1 year ago

Also, I see CLibrary does:

static {
        LOADED = JansiLoader.initialize();
        if (LOADED) {
            init();
        }
    }

If it's possible to move that and other similar initializations to non static blocks, that might also work.

xtaixe commented 1 year ago

Still getting the same error on CI builds though:

Failed to load native library:jansi-2.4.0-ef27179001bc2f21-libjansi.so. osinfo: Linux/x86_64
java.lang.UnsatisfiedLinkError: Can't load library: /tmp/jansi-2.4.0-ef27179001bc2f21-libjansi.so
rsenden commented 1 year ago

Still getting the same error on CI builds though:

Failed to load native library:jansi-2.4.0-ef27179001bc2f21-libjansi.so. osinfo: Linux/x86_64
java.lang.UnsatisfiedLinkError: Can't load library: /tmp/jansi-2.4.0-ef27179001bc2f21-libjansi.so

@xtaixe Any chance you are building a statically linked image in your CI builds? If so, see #246 on why you may be seeing this error.

xtaixe commented 1 year ago

@rsenden yes, that is correct. I recently discovered this was happening when static linking, but didn't have time to investigate further.