picocli-jansi-graalvm
Helper library for using Jansi in GraalVM native images.
Create native Windows executable command line applications with colors in Java.
Introduction
GraalVM now offers experimental support for Windows native images, so it is now possible to build applications in Java and compile them to a native Windows executable that does not require a JVM to be installed and has extremely fast startup time and lower memory requirements.
By building your command line application with the picocli library you get ANSI colors and styles for free, and you naturally want this functionality when building a native Windows executable.
The Jansi library makes it easy to enable ANSI escape codes in the cmd.exe
console or PowerShell console. Unfortunately, the Jansi library (as of version 1.18) by itself is not sufficient to show show colors in the console when running as a GraalVM native image in Windows.
picocli-jansi-graalvm
is a helper library that enables the use of ANSI escape codes in GraalVM native image applications running on Windows.
Usage
The picocli.jansi.graalvm.AnsiConsole
class can be used as a drop-in replacement of the standard Jansi org.fusesource.jansi.AnsiConsole
class to enable the Jansi ANSI support, either when running on the JVM or as a native image application.
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.jansi.graalvm.AnsiConsole; // not org.fusesource.jansi.AnsiConsole
@Command(name = "myapp", mixinStandardHelpOptions = true, version = "1.0",
description = "Example native CLI app with colors")
public class MyApp implements Runnable {
@Option(names = "--user", description = "User name")
String user;
public void run() {
System.out.printf("Hello, %s%n", user);
}
public static void main(String[] args) {
try (AnsiConsole ansi = AnsiConsole.windowsInstall()) { // enable colors on Windows
new CommandLine(new MyApp()).execute(args);
} // Closeable does cleanup when done
}
}
If you require other Jansi functionality than AnsiConsole
, call the Workaround.enableLibraryLoad()
method before invoking the Jansi code. For example:
import org.fusesource.jansi.internal.WindowsSupport;
import picocli.jansi.graalvm.Workaround;
public class OtherApp {
static {
Workaround.enableLibraryLoad();
}
public static void main(String[] args) {
int width = WindowsSupport.getWindowsTerminalWidth();
doCoolStuff(width);
}
// ...
}
See the Jansi README for more details on what Jansi can do.
Dependencies
The picocli-jansi-graalvm
library depends on org.fusesource.jansi
and nothing else.
In your project, we recommend you use the following dependencies:
// Gradle example
dependencies {
compile "info.picocli:picocli:4.3.2"
compile "info.picocli:picocli-jansi-graalvm:1.2.0"
compile "org.fusesource.jansi:jansi:1.18"
annotationProcessor "info.picocli:picocli-codegen:4.3.2"
}
See the example project.
Generating a native image for Windows
There are three steps to set up the toolchain for building native images on Windows. First, install the latest version of GraalVM, 19.2.1 as of this writing. Next, install the Microsoft Windows SDK for Windows 7 and .NET Framework 4 as well as the C compilers from KB2519277. The easiest way to install these is using chocolatey:
choco install windows-sdk-7.1 kb2519277
Finally (from the cmd prompt), activate the sdk-7.1 environment:
call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd"
This starts a new Command Prompt, with the sdk-7.1 environment enabled. Run all subsequent commands in this Command Prompt window. This completes the toolchain setup.
You can now generate a native image for your application by calling the native-image
generator tool in the %GRAAL_HOME%\bin
directory. For example:
set GRAAL_HOME=C:\apps\graalvm-ce-19.2.1
:: compile our my.pkg.MyApp class (assuming the source is in the .\src directory)
mkdir classes
javac -cp ^
.;picocli-4.3.2.jar;picocli-codegen-4.3.2.jar;jansi-1.18.jar;picocli-jansi-graalvm-1.2.0.jar ^
-sourcepath src ^
-d classes src\my\pkg\MyApp.java
:: create a jar
cd classes && jar -cvef my.pkg.MyApp ../myapp.jar * && cd ..
:: generate native image
%GRAAL_HOME%\bin\native-image ^
-cp picocli-4.3.2.jar;jansi-1.18.jar;picocli-jansi-graalvm-1.2.0.jar;myapp.jar ^
my.pkg.MyApp myapp
This creates a myapp.exe
Windows executable in the current directory for the my.pkg.MyApp
class.
Note that there is a known issue with Windows native images generated with Graal 19.2.1: the msvcr100.dll
library is required as an external dependency. This file is not always present on a Windows 10 system, so we recommend that you distribute the msvcr100.dll
file (you can find it in the C:\Windows\System32
directory) together with your Windows native image.
When do we need picocli-jansi-graalvm?
When you want to generate a Windows GraalVM native image for your application. Other platforms support ANSI escape codes out of the box so Jansi is not needed.
Why do we need picocli-jansi-graalvm?
When generating a native image, we need two configuration files for Jansi:
- JNI - Jansi uses JNI, and all classes, methods, and fields that should be accessible via JNI must be specified during native image generation in a configuration file. This library adds a
/META-INF/native-image/picocli-jansi-graalvm/jni-config.json
configuration file for the Jansiorg.fusesource.jansi.internal.CLibrary
andorg.fusesource.jansi.internal.Kernel32
classes. - resources - to get a single executable we need to bundle the jansi.dll in the native image. We need some configuration to ensure the jansi.dll is included as a resource. This library adds a
/META-INF/native-image/picocli-jansi-graalvm/resource-config.json
configuration file that ensures the/META-INF/native/windows64/jansi.dll
file is included as a resource in the native image.
By including these configuration files in our JAR file, developers can simply put this JAR in the classpath when creating a native image; no command line options are necessary.
Also, when using Jansi without this library, there is a problem extracting the jansi.dll
from the native image. The org.fusesource.hawtjni.runtime.Library
(in jansi 1.18) uses non-standard system properties to determine the bitMode of the platform, and these system properties are not available in SubstrateVM (the Graal native image JVM). As a result, the native library embedded in the Jansi JAR under /META-INF/native/windows64/jansi.dll
cannot be extracted from the native image even when it is included as a resource.
The picocli.jansi.graalvm.Workaround
class provides a workaround for this.
I filed a ticket for the above on the Jansi issue tracker, and this project will become obsolete if the Jansi library maintainers accept the pull requests I proposed, or fix these problems in some other way.
JNI Configuration Generator
The jni-config.json
file contains JNI configuration for all classes, methods and fields in org.fusesource.jansi.internal.CLibrary
and org.fusesource.jansi.internal.Kernel32
.
The following command can be used to regenerate it:
java -cp picocli-4.3.2.jar;jansi-1.18.jar;picocli-codegen-4.3.2.jar ^
picocli.codegen.aot.graalvm.JniConfigGenerator ^
org.fusesource.jansi.internal.CLibrary ^
org.fusesource.jansi.internal.Kernel32 ^
-o=.\jni-config.json