f native image, just build a jar

This commit is contained in:
Gregor Lohaus
2026-05-29 12:33:32 +02:00
parent 08ad025f76
commit b98a18b49f
12 changed files with 128 additions and 447 deletions

View File

@@ -16,19 +16,16 @@ import java.util.Map;
/**
* A Linux PTY backed by libc via the Foreign Function & Memory API.
*
* <p>This replaces pty4j (which loads a JNA JNI shim that does not initialise under a
* GraalVM native image). It uses {@code posix_openpt}/{@code posix_spawnp} rather than
* {@code fork}/{@code forkpty}: doing work between {@code fork} and {@code exec} inside a
* multithreaded JVM is unsafe (only async-signal-safe calls are permitted), whereas
* {@code posix_spawn} performs the dangerous part in libc with no Java on the stack.
* <p>This replaces pty4j (which loads a JNA JNI shim). It uses
* {@code posix_openpt}/{@code posix_spawnp} rather than {@code fork}/{@code forkpty}:
* doing work between {@code fork} and {@code exec} inside a multithreaded JVM is unsafe
* (only async-signal-safe calls are permitted), whereas {@code posix_spawn} performs the
* dangerous part in libc with no Java on the stack.
*
* <p>The child gets a fresh session via {@code POSIX_SPAWN_SETSID}; it then opens the slave
* PTY itself (as fd 0, without {@code O_NOCTTY}) so the slave becomes its controlling
* terminal. glibc applies attribute flags (the setsid) before file actions, so the open
* happens in the new session.
*
* <p>FFM downcall descriptors are registered for native image by
* {@link PtyForeignRegistrationFeature}; keep the two in sync.
*/
public final class LinuxPty implements AutoCloseable {
static final Linker LINKER = Linker.nativeLinker();
@@ -40,7 +37,7 @@ public final class LinuxPty implements AutoCloseable {
static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long");
static final ValueLayout.OfLong C_SIZE_T = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("size_t");
// Function descriptors. Mirrored in PtyForeignRegistrationFeature.
// Function descriptors.
static final FunctionDescriptor FD_INT_INT = FunctionDescriptor.of(C_INT, C_INT);
static final FunctionDescriptor FD_PTSNAME_R = FunctionDescriptor.of(C_INT, C_INT, C_POINTER, C_SIZE_T);
static final FunctionDescriptor FD_RW = FunctionDescriptor.of(C_LONG, C_INT, C_POINTER, C_SIZE_T);

View File

@@ -1,39 +0,0 @@
package com.gregor.jprototerm;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeForeignAccess;
import java.lang.foreign.Linker;
/**
* Registers the FFM downcall descriptors used by {@link LinuxPty} for GraalVM releases that
* do not consume {@code foreign.downcalls} from reachability-metadata.json. Mirrors
* jlibghostty's {@code GhosttyForeignRegistrationFeature}.
*
* <p>Wired in via {@code --features=com.gregor.jprototerm.PtyForeignRegistrationFeature}
* in the gluonfx compiler args.
*/
public final class PtyForeignRegistrationFeature implements Feature {
@Override
public void duringSetup(DuringSetupAccess access) {
downcall(LinuxPty.FD_INT_INT); // posix_openpt / grantpt / unlockpt / close
downcall(LinuxPty.FD_PTSNAME_R); // ptsname_r
downcall(LinuxPty.FD_RW); // read / write
downcall(LinuxPty.FD_KILL); // kill
downcall(LinuxPty.FD_WAITPID); // waitpid
downcall(LinuxPty.FD_SPAWN); // posix_spawnp
downcall(LinuxPty.FD_FA_INIT); // *_init / *_destroy
downcall(LinuxPty.FD_FA_ADDCLOSE); // posix_spawn_file_actions_addclose
downcall(LinuxPty.FD_FA_ADDDUP2); // posix_spawn_file_actions_adddup2
downcall(LinuxPty.FD_FA_ADDOPEN); // posix_spawn_file_actions_addopen
downcall(LinuxPty.FD_FA_ADDCHDIR); // posix_spawn_file_actions_addchdir_np
downcall(LinuxPty.FD_ATTR_SETFLAGS); // posix_spawnattr_setflags
// ioctl(int, unsigned long, ...) is variadic; register with the same linker option.
RuntimeForeignAccess.registerForDowncall(LinuxPty.FD_IOCTL, Linker.Option.firstVariadicArg(2));
}
private static void downcall(java.lang.foreign.FunctionDescriptor descriptor) {
RuntimeForeignAccess.registerForDowncall(descriptor);
}
}

View File

@@ -121,13 +121,24 @@ public final class TerminalCanvasView {
String cacheKey = paneCacheKey(pane, metrics);
int imageWidth = Math.max(1, (int) Math.ceil(pane.width()));
int imageHeight = Math.max(1, (int) Math.ceil(pane.height()));
if (cache.image == null || cache.canvas == null || cache.imageWidth != imageWidth || cache.imageHeight != imageHeight || !cacheKey.equals(cache.key)) {
// Allocate the offscreen buffers only when the pane size changes. Reallocating a
// full-pane Canvas + WritableImage on every content change churns ~20 MB per frame,
// which the native image's serial GC turns into Full-GC frame drops.
if (cache.canvas == null || cache.image == null || cache.imageWidth != imageWidth || cache.imageHeight != imageHeight) {
cache.canvas = new Canvas(imageWidth, imageHeight);
drawPaneContent(cache.canvas.getGraphicsContext2D(), pane, font, metrics, 0.0, 0.0, imageWidth, imageHeight, true);
cache.image = new WritableImage(imageWidth, imageHeight);
cache.canvas.snapshot(null, cache.image);
cache.imageWidth = imageWidth;
cache.imageHeight = imageHeight;
cache.key = null;
}
// Redraw and re-snapshot into the existing buffers only when content changed.
if (!cacheKey.equals(cache.key)) {
GraphicsContext cacheGc = cache.canvas.getGraphicsContext2D();
cacheGc.clearRect(0, 0, imageWidth, imageHeight);
drawPaneContent(cacheGc, pane, font, metrics, 0.0, 0.0, imageWidth, imageHeight, true);
cache.canvas.snapshot(null, cache.image);
cache.key = cacheKey;
}

View File

View File

@@ -1,5 +0,0 @@
{
"resources": {
"includes": []
}
}