diff --git a/build.gradle.kts b/build.gradle.kts index 03b1be1..1737c9f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,16 @@ java { withSourcesJar() } +val graalvmHome = providers.environmentVariable("GRAALVM_HOME") + .orElse(providers.systemProperty("java.home")) +val graalNativeImageJmod = graalvmHome.map { + file("$it/jmods/org.graalvm.nativeimage.jmod") +} + +dependencies { + compileOnly(files(graalNativeImageJmod)) +} + tasks.withType().configureEach { options.release.set(22) } diff --git a/flake.nix b/flake.nix index ef115bf..595c93b 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,7 @@ else if pkgs ? jdk24_headless then pkgs.jdk24_headless else if pkgs ? jdk24 then pkgs.jdk24 else pkgs.jdk; + graalvm = pkgs.graalvmPackages.graalvm-ce; version = "0.1.0-SNAPSHOT"; groupPath = "dev/jlibghostty"; @@ -79,7 +80,7 @@ cp "$ghostty_lib" "$bundled_lib" find src/main/java -name '*.java' | sort > build/sources.txt - javac --release 22 -d build/classes @build/sources.txt + javac --release 22 --module-path ${graalvm}/jmods -d build/classes @build/sources.txt jar --create \ --file build/${artifactId}-${version}.jar \ @@ -149,14 +150,17 @@ POM else if pkgs ? jdk24_headless then pkgs.jdk24_headless else if pkgs ? jdk24 then pkgs.jdk24 else pkgs.jdk; + graalvm = pkgs.graalvmPackages.graalvm-ce; ghosttyVt = ghostty.packages.${system}.libghostty-vt; in { default = pkgs.mkShell { packages = - [ jdk pkgs.gradle ] + [ jdk pkgs.gradle graalvm ] ++ pkgs.lib.optional (pkgs ? jextract) pkgs.jextract; + GRAALVM_HOME = "${graalvm}"; + JLIBGHOSTTY_LIBRARY = if pkgs.stdenv.hostPlatform.isDarwin then "${ghosttyVt}/lib/libghostty-vt.dylib" diff --git a/src/main/java/dev/jlibghostty/GhosttyForeignRegistrationFeature.java b/src/main/java/dev/jlibghostty/GhosttyForeignRegistrationFeature.java new file mode 100644 index 0000000..f5df907 --- /dev/null +++ b/src/main/java/dev/jlibghostty/GhosttyForeignRegistrationFeature.java @@ -0,0 +1,134 @@ +package dev.jlibghostty; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeForeignAccess; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; + +/** + * Registers FFM descriptors for GraalVM releases that do not consume + * foreign.downcalls from reachability-metadata.json. + */ +public final class GhosttyForeignRegistrationFeature implements Feature { + 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_LONG_LONG = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long long"); + private static final ValueLayout.OfLong C_SIZE_T = sizeTLayout(); + + private static final GroupLayout TERMINAL_OPTIONS = MemoryLayout.structLayout( + C_SHORT.withName("cols"), + C_SHORT.withName("rows"), + MemoryLayout.paddingLayout(4), + C_SIZE_T.withName("max_scrollback") + ); + + private static final GroupLayout SIZE_REPORT_SIZE = MemoryLayout.structLayout( + C_SHORT.withName("rows"), + C_SHORT.withName("columns"), + C_INT.withName("cell_width"), + C_INT.withName("cell_height") + ); + + private static final GroupLayout SCROLL_VIEWPORT = MemoryLayout.structLayout( + C_INT.withName("tag"), + MemoryLayout.paddingLayout(4), + MemoryLayout.structLayout( + C_LONG_LONG.withName("delta"), + C_LONG_LONG.withName("padding") + ).withName("value") + ); + + private static final GroupLayout MOUSE_POSITION = MemoryLayout.structLayout( + ValueLayout.JAVA_FLOAT.withName("x"), + ValueLayout.JAVA_FLOAT.withName("y") + ); + + private static final GroupLayout FORMATTER_SCREEN_EXTRA = MemoryLayout.structLayout( + C_SIZE_T.withName("size"), + C_BOOL.withName("cursor"), + C_BOOL.withName("style"), + C_BOOL.withName("hyperlink"), + C_BOOL.withName("protection"), + C_BOOL.withName("kitty_keyboard"), + C_BOOL.withName("charsets"), + MemoryLayout.paddingLayout(2) + ); + + private static final GroupLayout FORMATTER_TERMINAL_EXTRA = MemoryLayout.structLayout( + C_SIZE_T.withName("size"), + C_BOOL.withName("palette"), + C_BOOL.withName("modes"), + C_BOOL.withName("scrolling_region"), + C_BOOL.withName("tabstops"), + C_BOOL.withName("pwd"), + C_BOOL.withName("keyboard"), + MemoryLayout.paddingLayout(2), + FORMATTER_SCREEN_EXTRA.withName("screen") + ); + + private static final GroupLayout FORMATTER_TERMINAL_OPTIONS = MemoryLayout.structLayout( + C_SIZE_T.withName("size"), + C_INT.withName("emit"), + C_BOOL.withName("unwrap"), + C_BOOL.withName("trim"), + MemoryLayout.paddingLayout(2), + FORMATTER_TERMINAL_EXTRA.withName("extra"), + C_POINTER.withName("selection") + ); + + @Override + public void duringSetup(DuringSetupAccess access) { + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, TERMINAL_OPTIONS)); + downcall(FunctionDescriptor.ofVoid(C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_SHORT, C_SHORT, C_INT, C_INT)); + downcall(FunctionDescriptor.ofVoid(C_POINTER, SCROLL_VIEWPORT)); + downcall(FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_SIZE_T)); + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER)); + downcall(FunctionDescriptor.of(C_BOOL, C_POINTER, C_SIZE_T)); + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_SIZE_T, C_BOOL, C_POINTER, C_SIZE_T, C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_INT, C_POINTER)); + downcall(FunctionDescriptor.of(C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_INT, C_POINTER, C_SIZE_T, C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_INT, C_INT, C_POINTER, C_SIZE_T, C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_INT, SIZE_REPORT_SIZE, C_POINTER, C_SIZE_T, C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, FORMATTER_TERMINAL_OPTIONS)); + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_SIZE_T, C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER)); + downcall(FunctionDescriptor.of(C_BOOL, C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_POINTER)); + downcall(FunctionDescriptor.ofVoid(C_POINTER, C_INT)); + downcall(FunctionDescriptor.ofVoid(C_POINTER, C_SHORT)); + downcall(FunctionDescriptor.ofVoid(C_POINTER, MOUSE_POSITION)); + downcall(FunctionDescriptor.ofVoid(C_POINTER, C_INT, C_POINTER)); + downcall(FunctionDescriptor.ofVoid(C_POINTER, C_POINTER)); + downcall(FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_SIZE_T, C_POINTER)); + downcall(FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT)); + + upcall(FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_POINTER, C_SIZE_T)); + upcall(FunctionDescriptor.of(C_BOOL, C_POINTER, C_POINTER, C_POINTER)); + } + + private static void downcall(FunctionDescriptor descriptor) { + RuntimeForeignAccess.registerForDowncall(descriptor); + } + + private static void upcall(FunctionDescriptor descriptor) { + RuntimeForeignAccess.registerForUpcall(descriptor); + } + + private static ValueLayout.OfLong sizeTLayout() { + ValueLayout layout = (ValueLayout) LINKER.canonicalLayouts().get("size_t"); + if (layout.byteSize() != Long.BYTES) { + throw new UnsupportedOperationException("jlibghostty currently supports 64-bit platforms only"); + } + return (ValueLayout.OfLong) layout; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f59984a..a66b218 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,3 +1,5 @@ module dev.jlibghostty { + requires static org.graalvm.nativeimage; + exports dev.jlibghostty; } diff --git a/src/main/resources/META-INF/native-image/dev.jlibghostty/jlibghostty/native-image.properties b/src/main/resources/META-INF/native-image/dev.jlibghostty/jlibghostty/native-image.properties new file mode 100644 index 0000000..0b686c6 --- /dev/null +++ b/src/main/resources/META-INF/native-image/dev.jlibghostty/jlibghostty/native-image.properties @@ -0,0 +1,4 @@ +Args = --features=dev.jlibghostty.GhosttyForeignRegistrationFeature \ + -H:+UnlockExperimentalVMOptions \ + -H:+ForeignAPISupport \ + --enable-native-access=ALL-UNNAMED,dev.jlibghostty