Skip to content

Commit

Permalink
Support static native images
Browse files Browse the repository at this point in the history
  • Loading branch information
Glavo committed Oct 4, 2023
1 parent a8961cb commit a95a0ac
Show file tree
Hide file tree
Showing 23 changed files with 609 additions and 155 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ jobs:
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: mvn verify
run: mvn test
run: mvn verify -Dnosign
51 changes: 37 additions & 14 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 Expand Up @@ -379,20 +387,6 @@
<artifactId>maven-release-plugin</artifactId>
<version>3.0.0-M1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<goals>
<goal>sign</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
Expand Down Expand Up @@ -465,4 +459,33 @@
</extensions>
</build>

<profiles>
<profile>
<id>sign</id>
<activation>
<property>
<name>!nosign</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<goals>
<goal>sign</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

</project>
1 change: 1 addition & 0 deletions src/main/java/org/fusesource/jansi/AnsiMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public static void main(String... args) throws IOException {
System.out.println("java.version= " + System.getProperty("java.version") + ", "
+ "java.vendor= " + System.getProperty("java.vendor") + ","
+ " java.home= " + System.getProperty("java.home"));
System.out.println("Console: " + System.console());

System.out.println();

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
Expand Up @@ -121,7 +121,7 @@ private static String waitAndCapture(Process p) throws IOException, InterruptedE
/**
* This requires --add-opens java.base/java.lang=ALL-UNNAMED
*/
private ProcessBuilder.Redirect getRedirect(FileDescriptor fd) throws ReflectiveOperationException {
private static ProcessBuilder.Redirect getRedirect(FileDescriptor fd) throws ReflectiveOperationException {
// This is not really allowed, but this is the only way to redirect the output or error stream
// to the input. This is definitely not something you'd usually want to do, but in the case of
// the `tty` utility, it provides a way to get
Expand Down
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 a95a0ac

Please sign in to comment.