Skip to content

Commit

Permalink
Support using FFM in native-image (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
Glavo authored Oct 4, 2023
1 parent 1e4edb5 commit 9d60bc5
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 124 deletions.
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@
</properties>

<dependencies>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>nativeimage</artifactId>
<version>23.1.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down Expand Up @@ -260,6 +267,7 @@
<configuration>
<jvmVersion>9</jvmVersion>
<module>
<mainClass>org.fusesource.jansi.AnsiMain</mainClass>
<moduleInfo>
<name>org.fusesource.jansi</name>
<exports>org.fusesource.jansi;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

import org.fusesource.jansi.io.AnsiProcessor;

public interface AnsiConsoleSupport {
public abstract class AnsiConsoleSupport {

interface CLibrary {
public interface CLibrary {

int STDOUT_FILENO = 1;
int STDERR_FILENO = 2;
Expand All @@ -32,7 +32,7 @@ interface CLibrary {
int isTty(int fd);
}

interface Kernel32 {
public interface Kernel32 {

int isTty(long console);

Expand All @@ -51,9 +51,39 @@ interface Kernel32 {
AnsiProcessor newProcessor(OutputStream os, long console) throws IOException;
}

String getProviderName();
private final String providerName;
private CLibrary cLibrary;
private Kernel32 kernel32;

CLibrary getCLibrary();
protected AnsiConsoleSupport(String providerName) {
this.providerName = providerName;
}

public final String getProviderName() {
return providerName;
}

protected abstract CLibrary createCLibrary();

protected abstract Kernel32 createKernel32();

public final CLibrary getCLibrary() {
if (cLibrary == null) {
cLibrary = createCLibrary();
}

Kernel32 getKernel32();
return cLibrary;
}

public final Kernel32 getKernel32() {
if (kernel32 == null) {
if (!OSInfo.isWindows()) {
throw new RuntimeException("Kernel32 is not available on this platform");
}

kernel32 = createKernel32();
}

return kernel32;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,20 @@

public final class AnsiConsoleSupportHolder {

private static final String PROVIDER_NAME;
private static final AnsiConsoleSupport.CLibrary CLIBRARY;
private static final AnsiConsoleSupport.Kernel32 KERNEL32;
private static final Throwable ERR;
static final AnsiConsoleSupport PROVIDER;

static final Throwable ERR;

private static AnsiConsoleSupport getDefaultProvider() {
try {
// Call the specialized constructor to check whether the module has native access enabled
// If not, fallback to JNI to avoid the JDK printing warnings in stderr
return (AnsiConsoleSupport) Class.forName("org.fusesource.jansi.internal.ffm.AnsiConsoleSupportImpl")
.getConstructor(boolean.class)
.newInstance(true);
} catch (Throwable ignored) {
if (!OSInfo.isInImageCode()) {
try {
// Call the specialized constructor to check whether the module has native access enabled
// If not, fallback to JNI to avoid the JDK printing warnings in stderr
return (AnsiConsoleSupport) Class.forName("org.fusesource.jansi.internal.ffm.AnsiConsoleSupportImpl")
.getConstructor(boolean.class)
.newInstance(true);
} catch (Throwable ignored) {
}
}

return new org.fusesource.jansi.internal.jni.AnsiConsoleSupportImpl();
Expand Down Expand Up @@ -81,47 +82,26 @@ private static AnsiConsoleSupport findProvider(String providerList) {
err = e;
}

String providerName = null;
AnsiConsoleSupport.CLibrary clib = null;
AnsiConsoleSupport.Kernel32 kernel32 = null;
PROVIDER = ansiConsoleSupport;
ERR = err;
}

if (ansiConsoleSupport != null) {
try {
providerName = ansiConsoleSupport.getProviderName();
clib = ansiConsoleSupport.getCLibrary();
kernel32 = OSInfo.isWindows() ? ansiConsoleSupport.getKernel32() : null;
} catch (Throwable e) {
err = e;
}
public static AnsiConsoleSupport getProvider() {
if (PROVIDER == null) {
throw new RuntimeException("No provider available", ERR);
}

PROVIDER_NAME = providerName;
CLIBRARY = clib;
KERNEL32 = kernel32;
ERR = err;
return PROVIDER;
}

public static String getProviderName() {
return PROVIDER_NAME;
return getProvider().getProviderName();
}

public static AnsiConsoleSupport.CLibrary getCLibrary() {
if (CLIBRARY == null) {
throw new RuntimeException("Unable to get the instance of CLibrary", ERR);
}

return CLIBRARY;
return getProvider().getCLibrary();
}

public static AnsiConsoleSupport.Kernel32 getKernel32() {
if (KERNEL32 == null) {
if (OSInfo.isWindows()) {
throw new RuntimeException("Unable to get the instance of Kernel32", ERR);
} else {
throw new UnsupportedOperationException("Not Windows");
}
}

return KERNEL32;
return getProvider().getKernel32();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2009-2023 the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.jansi.internal;

import java.util.Objects;

import org.fusesource.jansi.AnsiConsole;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
import org.graalvm.nativeimage.hosted.RuntimeSystemProperties;

public class NativeImageFeature implements Feature {
@Override
public String getURL() {
return "https://github.com/fusesource/jansi";
}

@Override
public void duringSetup(DuringSetupAccess access) {
RuntimeClassInitialization.initializeAtBuildTime(AnsiConsoleSupportHolder.class);

String providers = System.getProperty(AnsiConsole.JANSI_PROVIDERS);
if (providers != null) {
try {
RuntimeSystemProperties.register(AnsiConsole.JANSI_PROVIDERS, providers);
} catch (Throwable ignored) {
// GraalVM version < 23.0
// No need to worry as we select the provider at build time
}
}

String provider = Objects.requireNonNull(AnsiConsoleSupportHolder.getProviderName(), "No provider available");
if (provider.equals(AnsiConsole.JANSI_PROVIDER_JNI)) {
String jansiNativeLibraryName = System.mapLibraryName("jansi");
if (jansiNativeLibraryName.endsWith(".dylib")) {
jansiNativeLibraryName = jansiNativeLibraryName.replace(".dylib", ".jnilib");
}

String packagePath = JansiLoader.class.getPackage().getName().replace('.', '/');

try {
Class<?> moduleClass = Class.forName("java.lang.Module");
Class<?> rraClass = Class.forName("org.graalvm.nativeimage.hosted.RuntimeResourceAccess");

Object module = Class.class.getMethod("getModule").invoke(JansiLoader.class);
rraClass.getMethod("addResource", moduleClass, String.class)
.invoke(
null,
module,
String.format(
"%s/native/%s/%s",
packagePath,
OSInfo.getNativeLibFolderPathForCurrentOS(),
jansiNativeLibraryName));

} catch (Throwable ignored) {
// GraalVM version < 22.3
// Users need to manually add the JNI library as resources
}
} else if (provider.equals(AnsiConsole.JANSI_PROVIDER_FFM)) {
try {
// FFM is only available in JDK 21+, so we need to compile it separately
Class.forName("org.fusesource.jansi.internal.ffm.NativeImageDowncallRegister")
.getMethod("registerForDowncall")
.invoke(null);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
}
Loading

0 comments on commit 9d60bc5

Please sign in to comment.