Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support using FFM in native-image #269

Merged
merged 16 commits into from
Oct 4, 2023
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
Loading