graal conf

This commit is contained in:
Gregor Lohaus
2026-05-27 14:11:20 +02:00
parent be0c0bb321
commit a03bc2ec48
4 changed files with 176 additions and 24 deletions

View File

@@ -37,6 +37,85 @@ tasks.withType<JavaExec>().configureEach {
}
```
## GraalVM Native Image Consumer
`jlibghostty` ships GraalVM reachability metadata at:
```text
META-INF/native-image/dev.jlibghostty/jlibghostty/reachability-metadata.json
```
That metadata registers the FFM downcalls used by this library and includes the bundled native library resource. GraalVM 25 enables Native Image FFM support by default, but the build still needs native access:
```sh
native-image --enable-native-access=ALL-UNNAMED ...
```
If your app uses the module path, prefer:
```sh
native-image --enable-native-access=dev.jlibghostty ...
```
For a Nix-built downstream project, the usual shape is:
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
jlibghostty.url = "path:/home/anon/Dev/jlibghostty";
};
outputs = { nixpkgs, jlibghostty, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
jlib = jlibghostty.packages.${system}.jlibghostty;
in {
packages.${system}.default = pkgs.stdenvNoCC.mkDerivation {
pname = "my-native-app";
version = "0.1.0";
src = ./.;
nativeBuildInputs = [
pkgs.graalvmPackages.graalvm-ce
pkgs.gradle
];
buildPhase = ''
export GRADLE_USER_HOME=$TMPDIR/gradle
gradle --offline -PjlibghosttyMavenRepo=${jlib}/maven installDist
native-image \
--enable-native-access=ALL-UNNAMED \
-cp build/install/my-app/lib/'*' \
-o my-app \
com.example.Main
'';
installPhase = ''
mkdir -p "$out/bin"
cp my-app "$out/bin/"
'';
};
};
}
```
In that downstream Gradle build:
```kotlin
repositories {
mavenCentral()
maven {
url = uri(providers.gradleProperty("jlibghosttyMavenRepo").get())
}
}
dependencies {
implementation("dev.jlibghostty:jlibghostty:0.1.0-SNAPSHOT")
}
```
If the app runs on the module path, use:
```sh

View File

@@ -64,6 +64,9 @@
runHook preBuild
mkdir -p build/classes build/test-classes build/resources/${groupPath}/native/${platformName}
if [ -d src/main/resources ]; then
cp -R src/main/resources/. build/resources/
fi
ghostty_lib="$(find ${ghosttyVt} -type f -name '${sharedLibraryPattern}' -print -quit)"
if [ -z "$ghostty_lib" ]; then

View File

@@ -4,6 +4,7 @@ import dev.jlibghostty.GhosttyException;
import dev.jlibghostty.TerminalOptions;
import java.lang.foreign.Arena;
import java.lang.foreign.AddressLayout;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.GroupLayout;
import java.lang.foreign.Linker;
@@ -15,11 +16,7 @@ import java.lang.invoke.MethodHandle;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_SHORT;
public final class GhosttyLibrary {
public static final int TERMINAL_DATA_COLS = 1;
@@ -35,17 +32,21 @@ public final class GhosttyLibrary {
private static final int GHOSTTY_NO_VALUE = -4;
private static final Linker LINKER = Linker.nativeLinker();
private static final AddressLayout C_POINTER = (AddressLayout) LINKER.canonicalLayouts().get("void*");
private static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) LINKER.canonicalLayouts().get("bool");
private static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) LINKER.canonicalLayouts().get("short");
private static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) LINKER.canonicalLayouts().get("int");
private static final ValueLayout.OfLong C_SIZE_T = sizeTLayout();
private static final GroupLayout TERMINAL_OPTIONS = MemoryLayout.structLayout(
JAVA_SHORT.withName("cols"),
JAVA_SHORT.withName("rows"),
C_SHORT.withName("cols"),
C_SHORT.withName("rows"),
MemoryLayout.paddingLayout(4),
C_SIZE_T.withName("max_scrollback")
);
private static final GroupLayout GHOSTTY_STRING = MemoryLayout.structLayout(
ADDRESS.withName("ptr"),
C_POINTER.withName("ptr"),
C_SIZE_T.withName("len")
);
@@ -63,21 +64,21 @@ public final class GhosttyLibrary {
SymbolLookup symbols = SymbolLookup.libraryLookup(libraryPath, Arena.global());
terminalNew = downcall(symbols, "ghostty_terminal_new",
FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS, TERMINAL_OPTIONS));
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, TERMINAL_OPTIONS));
terminalFree = downcall(symbols, "ghostty_terminal_free",
FunctionDescriptor.ofVoid(ADDRESS));
FunctionDescriptor.ofVoid(C_POINTER));
terminalReset = downcall(symbols, "ghostty_terminal_reset",
FunctionDescriptor.ofVoid(ADDRESS));
FunctionDescriptor.ofVoid(C_POINTER));
terminalResize = downcall(symbols, "ghostty_terminal_resize",
FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_SHORT, JAVA_SHORT, JAVA_INT, JAVA_INT));
FunctionDescriptor.of(C_INT, C_POINTER, C_SHORT, C_SHORT, C_INT, C_INT));
terminalVtWrite = downcall(symbols, "ghostty_terminal_vt_write",
FunctionDescriptor.ofVoid(ADDRESS, ADDRESS, C_SIZE_T));
FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_SIZE_T));
terminalGet = downcall(symbols, "ghostty_terminal_get",
FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, ADDRESS));
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER));
pasteIsSafe = downcall(symbols, "ghostty_paste_is_safe",
FunctionDescriptor.of(JAVA_BOOLEAN, ADDRESS, C_SIZE_T));
FunctionDescriptor.of(C_BOOL, C_POINTER, C_SIZE_T));
pasteEncode = downcall(symbols, "ghostty_paste_encode",
FunctionDescriptor.of(JAVA_INT, ADDRESS, C_SIZE_T, JAVA_BOOLEAN, ADDRESS, C_SIZE_T, ADDRESS));
FunctionDescriptor.of(C_INT, C_POINTER, C_SIZE_T, C_BOOL, C_POINTER, C_SIZE_T, C_POINTER));
} catch (IllegalCallerException e) {
throw new IllegalStateException(
"FFM native access is disabled. Run with --enable-native-access=dev.jlibghostty "
@@ -93,17 +94,17 @@ public final class GhosttyLibrary {
public MemorySegment terminalNew(TerminalOptions options) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(ADDRESS);
MemorySegment out = arena.allocate(C_POINTER);
MemorySegment nativeOptions = arena.allocate(TERMINAL_OPTIONS);
nativeOptions.set(JAVA_SHORT, 0, (short) options.columns());
nativeOptions.set(JAVA_SHORT, 2, (short) options.rows());
nativeOptions.set(C_SHORT, 0, (short) options.columns());
nativeOptions.set(C_SHORT, 2, (short) options.rows());
nativeOptions.set(C_SIZE_T, 8, options.maxScrollback());
int result = (int) terminalNew.invoke(MemorySegment.NULL, out, nativeOptions);
checkResult("ghostty_terminal_new", result);
MemorySegment terminal = out.get(ADDRESS, 0);
MemorySegment terminal = out.get(C_POINTER, 0);
if (terminal.address() == 0) {
throw new IllegalStateException("ghostty_terminal_new returned a null terminal handle");
}
@@ -160,10 +161,10 @@ public final class GhosttyLibrary {
public int terminalGetU16(MemorySegment terminal, int key) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(JAVA_SHORT);
MemorySegment out = arena.allocate(C_SHORT);
int result = (int) terminalGet.invoke(terminal, key, out);
checkResult("ghostty_terminal_get", result);
return Short.toUnsignedInt(out.get(JAVA_SHORT, 0));
return Short.toUnsignedInt(out.get(C_SHORT, 0));
} catch (Throwable t) {
return rethrow(t);
}
@@ -171,10 +172,10 @@ public final class GhosttyLibrary {
public boolean terminalGetBoolean(MemorySegment terminal, int key) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment out = arena.allocate(JAVA_BOOLEAN);
MemorySegment out = arena.allocate(C_BOOL);
int result = (int) terminalGet.invoke(terminal, key, out);
checkResult("ghostty_terminal_get", result);
return out.get(JAVA_BOOLEAN, 0);
return out.get(C_BOOL, 0);
} catch (Throwable t) {
return rethrow(t);
}
@@ -189,7 +190,7 @@ public final class GhosttyLibrary {
}
checkResult("ghostty_terminal_get", result);
MemorySegment ptr = out.get(ADDRESS, 0);
MemorySegment ptr = out.get(C_POINTER, 0);
long len = out.get(C_SIZE_T, 8);
if (ptr.address() == 0 || len == 0) {
return "";

View File

@@ -0,0 +1,69 @@
{
"resources": [
{
"glob": "dev/jlibghostty/native/**"
}
],
"foreign": {
"downcalls": [
{
"returnType": "int",
"parameterTypes": [
"void*",
"void*",
"struct(short, short, padding(4), size_t)"
]
},
{
"returnType": "void",
"parameterTypes": [
"void*"
]
},
{
"returnType": "int",
"parameterTypes": [
"void*",
"short",
"short",
"int",
"int"
]
},
{
"returnType": "void",
"parameterTypes": [
"void*",
"void*",
"size_t"
]
},
{
"returnType": "int",
"parameterTypes": [
"void*",
"int",
"void*"
]
},
{
"returnType": "bool",
"parameterTypes": [
"void*",
"size_t"
]
},
{
"returnType": "int",
"parameterTypes": [
"void*",
"size_t",
"bool",
"void*",
"size_t",
"void*"
]
}
]
}
}