Closed remkop closed 3 years ago
In the cold light of the next morning I realize that a potentially much simpler solution would be to link jansi.dll
statically with the native image at compile time. I’ll investigate this option next.
I believe I found the reason why org.fusesource.hawtjni.runtime.Library
cannot extract jansi.dll
from the native image: its implementation of the getBitModel method depends on non-standard system properties which are not present in SubstrateVM (the native image JVM).
In a GraalVM native image, the getBitModel
method returns -1
, and the extractAndLoad
logic will try the following locations, all of which will fail:
/META-INF/native/windows-1/amd64/jansi.dll
/META-INF/native/windows-1/jansi.dll
/META-INF/native/windows/jansi.dll
To fix this, I propose we change the getBitModel
implementation to this:
public static int getBitModel() {
String prop = System.getProperty("sun.arch.data.model");
if (prop == null) {
prop = System.getProperty("com.ibm.vm.bitmode");
}
if (prop != null) {
return Integer.parseInt(prop);
}
// No 100% certainty... take an educated guess
// https://stackoverflow.com/questions/10846105/all-possible-values-os-arch-in-32bit-jre-and-in-64bit-jre
prop = System.getProperty("os.arch");
if (prop.endsWith("64")) {
return 64;
} else if (prop.endsWith("86")) {
return 32;
}
return -1; // we don't know..
}
UPDATE:
If there is a concern that this may give incorrect results on other platforms, we can limit this to GraalVM native images only:
...
prop = System.getProperty("os.arch");
if (prop.endsWith("64") && "Substrate VM".equals(System.getProperty("java.vm.name"))) {
return 64;
}
return -1; // we don't know...
Thoughts?
This has been fixed in 2.x.
Hi @gnodet, thanks for taking care of this.
Looking at the implemented solution, I see:
String arch = System.getProperty("os.arch");
if (arch.endsWith("64") && "Substrate VM".equals(System.getProperty("java.vm.name"))) {
return 64;
}
return -1; // we don't know..
Thanks for the fix!
Just wondering, is it ever a good idea to return -1? When -1 is returned, basically nothing works, right? So would it not be better to return 64 or 32 if the probability is high that this is a good match? (Because -1 means certain failure, if I understand correctly).
I wonder what you did not like about the solution I proposed:
if (arch.endsWith("64")) {
return 64;
} else if (arch.endsWith("86")) {
return 32;
}
return -1;
@remkop where do you see such code ?
@remkop Jansi 2.x does not use HawtJNI anymore.
@gnodet I see, so I commented on the wrong issue? I should have commented on https://github.com/fusesource/hawtjni/pull/61 instead. I just saw the PR was merged and did not realize it was changed... :-)
Still, my question still stands, what was the drawback of my proposed solution?
In the cold light of the next morning I realize that a potentially much simpler solution would be to link
jansi.dll
statically with the native image at compile time. I’ll investigate this option next.
@remkop Did you ever investigate statically linking the Jansi modules with native images any further? For our picocli-based application, we use GraalVM to build Linux, MacOS and Windows native images. The Linux image is statically linked using muslc, MacOS and Windows images are dynamically linked. I'm trying to add Jansi 2.4.0 (and later also JLine3). This works without much issues for the dynamically linked Windows/MacOS images, but not for the statically linked Linux image as muslc doesn't support dynamic module loading:
Failed to load native library:jansi-2.4.0-1cd5a486e620058e-libjansi.so. osinfo: Linux/x86_64
java.lang.UnsatisfiedLinkError: Can't load library: /tmp/jansi-2.4.0-1cd5a486e620058e-libjansi.so
I tried changing the Linux image to use dynamic linking, but then I get errors like the below when trying to run our application on WSL2 Ubuntu:
./fcli: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./fcli)
./fcli: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./fcli)
As such, I would prefer sticking to a statically linked native image, to avoid users of our application to run into these GLIBC version errors or similar issues, and also to be able to provide a FROM scratch
Docker image for our application.
Any suggestions/examples you can share?
Edit: I opened a new issue for adding support for statically linking the jansi library, which describes all the changes that I think would be required: #246
@rsenden No, I never investigated statically linking the Jansi modules with native images. (But see this potentially related discussion: https://github.com/oracle/graal/issues/1762#issuecomment-560527509)
@remkop Thanks for the link!
TL;DR - for Jansi Users
Jansi by itself is insufficient to show colors in Java applications compiled to GraalVM native images for Windows. This is partly because GraalVM requires configuration and partly because Jansi internally depends on non-standard system properties, without a graceful fallback if these properties are absent (as is the case in GraalVM).
Users may be interested in combining Jansi with picocli-jansi-graalvm until this issue is fixed.
For the Jansi Maintainers
Background
I'm working on picocli support for Graal native images. Building native images for Windows is still experimental, and it's not perfect but it works.
I would like to provide support for colored output on Windows console when executing a native image. Using Jansi for this is the obvious choice. (We don't need to worry about other OS-es.)
Would you be interested in helping to provide Jansi support for GraalVM native images on Windows?
Objective
Easily create a single Windows executable that shows colors on the console.
Problem Description
We need two configuration files to make Jansi work in a native image. If we can include these in the Jansi JAR file, it becomes very easy for developers to create native images.
Also, there is a problem extracting the jansi.dll from the native image. This should work similarly to extracting it from the jansi JAR, but there is some difference and
org.fusesource.hawtjni.runtime.Library
(in jansi 1.18) is unable to extract jansi.dll from the native image.What I've done so far
Created the below GraalVM configuration files:
I've created a
jni-config.json
file for all classes, methods and fields inorg.fusesource.jansi.internal.CLibrary
andorg.fusesource.jansi.internal.Kernel32
. (See attached jni-config.json.txt file.)(The jni-config.json config file can be re-generated with the below command if necessary: )
Secondly, we need to ensure that the
jansi.dll
is included in the native image, just like it is included in the Jansi JAR. To do this, we register it as a resource with GraalVM using configuration. The Jansi JAR file includes native libraries for many platforms, but we only need thejansi.dll
for 64-bit Windows. We can ensure this DLL is included in the native image by supplying thisresource-config.json
file:What I need from you
Summary:
Library
logic that extracts thejansi.dll
, when running as a native image. There is a workaround, but it would be great if theLibrary
logic itself could be fixed so that the workaround is not necessary.Please include GraalVM configuration in Jansi distribution going forward
Developers can specify the above two configuration files on the command line when creating a native image, but this is cumbersome.
If Jansi can include these two configuration files in the jansi-x.x.jar in the following location, then the GraalVM
native-image
generator tool will pick up the configuration automatically:Extraction Issue in hawtjni
Library
The configuration alone is not sufficient to get colored output from a native image. When I create a native image for a sample program that runs
AnsiMain
afterAnsiConsole.systemInstall()
, I get the following error:Setting the
library.jansi.path
system property to a writable directory did not help, resulting in a similar but longer error message:Cause: problem in extraction logic when running in native image
The problem does not manifest when the application extracts
jansi.dll
before callingAnsiConsole.systemInstall()
: there is noUnsatisfiedLinkError
, and the console shows colors! See the workaround below for details.So there is some problem in the hawtjni
Library
extraction logic that makes it fail when run in a GraalVM native image. I have not been able to determine what that problem is exactly.Would you be interested in helping me figure out where the problem is, and fixing the hawtjni
Library
extraction logic?Steps to reproduce:
Then (from the
cmd
prompt), activate the sdk-7.1 environment:This starts a new Command Prompt, with the sdk-7.1 environment enabled. Run all subsequent commands in this Command Prompt window.
Example app:
Here is the command to create the native image:
This will create a native image
myapp.exe
in the current directory. Executing this native image will show theUnsatisfiedLinkError
.Workaround: Extract
jansi.dll
in the applicationTo fix the
UnsatisfiedLinkError
and show colors, replace the main method inApp
with the below.Here is the code to “manually” extract the
jansi.dll
from the native image resource and add it to the java.library.path in the application (instead of relying onLibrary
:With this workaround, colors are shown on the console when running as a native image. It would be great if the
Library
logic itself could be fixed so that this workaround is not necessary.Sorry for the very long issue.