Files
jprototerm/flake.nix

197 lines
8.9 KiB
Nix

{
description = "jprototerm";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
jlibghostty.url = "git+https://gitea.gregorlohaus.com/gregor/jlibghostty.git";
ghostty.follows = "jlibghostty/ghostty";
};
outputs = { self, nixpkgs, jlibghostty, ghostty }:
let
supportedSystems = [ "x86_64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
# Everything the JavaFX natives (and jlibghostty) dlopen at runtime, EXCEPT the
# system OpenGL/graphics drivers. libGL is intentionally left out: it is supplied
# by the host at runtime via the GL shim in the wrapper below, so the same closure
# works on NixOS and on a plain Debian box with vendor GPU drivers installed.
runtimeLibsFor = pkgs: ghosttyVt: [
pkgs.glib
pkgs.gtk3
pkgs.pango
pkgs.cairo
pkgs.gdk-pixbuf
pkgs.harfbuzz
pkgs.freetype
pkgs.fontconfig.lib
pkgs.libx11
pkgs.libxext
pkgs.libxrender
pkgs.libxtst
pkgs.libxi
pkgs.libxcursor
pkgs.libxrandr
pkgs.libxinerama
pkgs.libxcb
pkgs.libxxf86vm
pkgs.zlib
ghosttyVt
];
in {
packages = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
jlib = jlibghostty.packages.${system}.jlibghostty;
ghosttyVt = ghostty.packages.${system}.libghostty-vt;
runtimeLibs = runtimeLibsFor pkgs ghosttyVt;
jprototerm = pkgs.stdenv.mkDerivation (finalAttrs: {
pname = "jprototerm";
version = "0.1.0";
src = ./.;
nativeBuildInputs = [
pkgs.jdk25
pkgs.gradle_9
pkgs.makeWrapper
];
buildInputs = runtimeLibs;
mitmCache = pkgs.gradle_9.fetchDeps {
pkg = finalAttrs.finalPackage;
data = ./deps.json;
useBwrap = false;
};
# Builds build/install/jprototerm/{bin,lib} with every runtime jar, including
# the maven javafx-*-linux jars that carry the platform natives.
gradleBuildTask = "installDist";
gradleFlags = [
"--no-build-cache"
"--stacktrace"
"-Dorg.gradle.java.home=${pkgs.jdk25}"
];
JAVA_HOME = "${pkgs.jdk25}";
JLIBGHOSTTY_MAVEN_REPO = "${jlib}/maven";
preBuild = ''
export HOME="$TMPDIR/home"
export GRADLE_OPTS="-Duser.home=$HOME ''${GRADLE_OPTS:-}"
'';
preGradleUpdate = ''
export HOME="$TMPDIR/home"
'';
installPhase = ''
runHook preInstall
mkdir -p "$out/share/jprototerm"
cp -a build/install/jprototerm/lib "$out/share/jprototerm/lib"
# JavaFX is a set of proper modular jars: put them on the module path and
# keep the application + plain dependency jars on the classpath, so the two
# worlds do not collide.
mkdir -p "$out/share/jprototerm/javafx"
mv "$out/share/jprototerm/lib"/javafx-*.jar "$out/share/jprototerm/javafx/"
# Build an explicit colon-separated classpath. A "lib/*" glob would be
# expanded by the wrapper's shell before java sees it, breaking -cp.
classpath=""
for jar in "$out"/share/jprototerm/lib/*.jar; do
classpath="$classpath''${classpath:+:}$jar"
done
# The CDS archive records launch-time module/classpath properties, including
# Nix store paths. Key it by this build's launch shape so stale archives from a
# previous package path cannot be reused, and pass the flags on java's command
# line so terminal child processes do not inherit them through JAVA_TOOL_OPTIONS.
cdsArchive="app-$(printf '%s\n' \
"${pkgs.jdk25}" \
"$out" \
"$classpath" \
"$out/share/jprototerm/javafx" \
"--enable-native-access=ALL-UNNAMED,javafx.graphics" \
"--add-modules=javafx.controls,javafx.fxml" \
"com.gregor.jprototerm.Main" \
| sha256sum | cut -c1-16).jsa"
# Prism frees GPU textures (and the X11 pixmaps behind them) only from phantom-ref
# Disposers that run during a GC. This daemon is one long-lived JVM whose ~140MB heap
# never nears the multi-GB default ceiling, so G1 almost never collects, the Disposers
# never run, and orphaned render resources (closed panes/windows, resized backbuffers)
# pile up until the X server's VRAM is exhausted and the whole session freezes. -Xmx
# makes output churn drive GC; G1PeriodicGCInterval runs a concurrent (low-pause) GC on
# an idle timer so an idle daemon still reclaims. The live heap is tiny, so GC is cheap.
makeWrapper "${pkgs.jdk25}/bin/java" "$out/bin/jprototerm" \
--run 'if [ "$#" -eq 0 ]; then if [ -n "''${XDG_RUNTIME_DIR:-}" ]; then jprototermSock="$XDG_RUNTIME_DIR/jprototerm/daemon.sock"; else jprototermSock="/tmp/jprototerm-''${USER:-user}/daemon.sock"; fi; if [ -S "$jprototermSock" ] && printf "%s\n" "$(pwd)" | ${pkgs.socat}/bin/socat - UNIX-CONNECT:"$jprototermSock" >/dev/null 2>&1; then exit 0; fi; fi' \
--run 'export JPROTOTERM_HOST_LD_LIBRARY_PATH="''${LD_LIBRARY_PATH:-}"' \
--run 'cdsDir="''${XDG_CACHE_HOME:-$HOME/.cache}/jprototerm"; mkdir -p "$cdsDir"' \
--add-flags "-XX:+AutoCreateSharedArchive" \
--add-flags "-XX:SharedArchiveFile=\$cdsDir/$cdsArchive" \
--add-flags "-Xmx512m" \
--add-flags "-XX:G1PeriodicGCInterval=5000" \
--add-flags "--enable-native-access=ALL-UNNAMED,javafx.graphics" \
--add-flags "--module-path $out/share/jprototerm/javafx" \
--add-flags "--add-modules javafx.controls,javafx.fxml" \
--add-flags "-cp $classpath" \
--add-flags "com.gregor.jprototerm.Main" \
--prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath runtimeLibs}" \
--run 'glShimDir="''${XDG_RUNTIME_DIR:-/tmp}/jprototerm-gl"; mkdir -p "$glShimDir"; for lib in /lib/x86_64-linux-gnu/libGL.so.1 /lib/x86_64-linux-gnu/libGLX.so.0 /lib/x86_64-linux-gnu/libGLdispatch.so.0 /usr/lib/x86_64-linux-gnu/libGLX_nvidia.so* /usr/lib/x86_64-linux-gnu/libEGL_nvidia.so* /usr/lib/x86_64-linux-gnu/libnvidia*.so* /usr/lib/x86_64-linux-gnu/nvidia/current/lib*.so*; do [ -e "$lib" ] && ln -sfn "$lib" "$glShimDir/$(basename "$lib")"; done; export LD_LIBRARY_PATH="$glShimDir''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"; export __GLX_VENDOR_LIBRARY_NAME="''${__GLX_VENDOR_LIBRARY_NAME:-nvidia}"; if [ -e /usr/share/glvnd/egl_vendor.d/10_nvidia.json ]; then export __EGL_VENDOR_LIBRARY_FILENAMES="''${__EGL_VENDOR_LIBRARY_FILENAMES:-/usr/share/glvnd/egl_vendor.d/10_nvidia.json}"; fi' \
--set JLIBGHOSTTY_LIBRARY "${ghosttyVt}/lib/libghostty-vt.so" \
--set GDK_BACKEND x11
# Optional background daemon: one JVM hosts every window, so client launches skip
# cold JVM/JavaFX/GL startup. A *user* service tied to graphical-session.target (X11
# needs a display, which only exists after login). Enable instructions are in README.
mkdir -p "$out/share/systemd/user"
cat > "$out/share/systemd/user/jprototerm.service" <<EOF
[Unit]
Description=jprototerm terminal daemon
PartOf=graphical-session.target
After=graphical-session.target
[Service]
Type=simple
ExecStart=$out/bin/jprototerm --daemon
Restart=on-failure
[Install]
WantedBy=graphical-session.target
EOF
runHook postInstall
'';
});
in {
default = jprototerm;
gradleDepsUpdateScript = jprototerm.mitmCache.updateScript;
});
devShells = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
jlib = jlibghostty.packages.${system}.jlibghostty;
ghosttyVt = ghostty.packages.${system}.libghostty-vt;
runtimeLibs = runtimeLibsFor pkgs ghosttyVt;
in {
default = pkgs.mkShell {
packages = [
pkgs.gradle_9
pkgs.jdk25
pkgs.jdt-language-server
] ++ runtimeLibs;
JLIBGHOSTTY_MAVEN_REPO = "${jlib}/maven";
JLIBGHOSTTY_LIBRARY = "${ghosttyVt}/lib/libghostty-vt.so";
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs;
};
});
};
}