-
Notifications
You must be signed in to change notification settings - Fork 0
Native binaries
glyphora treats GraalVM compatibility as an architecture rule, not a late packaging experiment. The runtime does not scan classes or use reflection; Scala 3 macros generate direct form and action wiring during compilation.
The result is a self-contained terminal executable built with --no-fallback and no
reflect-config.json.
./mill examples.showcase.nativeImageThe example module declares:
object `package` extends build.TuiModule with NativeImageModule:
def moduleDeps = Seq(
build.core,
build.terminal,
build.runtime,
build.widgets,
build.dsl,
)
def mainClass = Some("io.worxbend.tui.examples.showcase.Main")
def jvmVersion = "graalvm-community:23.0.1"
def nativeImageOptions = Seq("--no-fallback")Mill resolves the configured GraalVM toolchain and writes the executable under the
module's out/.../nativeImage.dest/ directory.
import mill.*, scalalib.*, javalib.NativeImageModule
object app extends ScalaModule with NativeImageModule:
def scalaVersion = "3.7.1"
def mvnDeps = Seq(mvn"io.worxbend::tui-dsl:0.10.0")
def mainClass = Some("example.Main")
def jvmVersion = "graalvm-community:23.0.1"
def nativeImageOptions = Seq("--no-fallback")Then build:
./mill app.nativeImageKeep --no-fallback: without it, native-image may silently produce a launcher that
still needs a JVM, hiding compatibility problems until release.
Closed-world compilation must know every reachable class and method. Reflection and dynamic class loading conceal that graph, which usually leads to hand-maintained configuration files that drift as code changes.
glyphora avoids the problem:
-
deriveForm[A]uses Scala 3Mirrorat compile time and emits direct calls; - event, widget, and state types are ordinary sealed/data structures;
- service discovery and runtime classpath scanning are absent;
- CI rejects
java.lang.reflectandClass.forNamein main Scala sources.
This rule also improves JVM behavior: fewer hidden code paths, clearer dependencies, and errors that appear during compilation instead of at runtime.
CI builds native executables for hello-world, counter, todo-list, dashboard,
form-demo, and showcase. It then launches each in a headless job and verifies a
clean UnsupportedTerminal response instead of a hang or corrupt raw-mode setup.
./out/examples/showcase/nativeImage.dest/native-executable
# no TTY: exits with an UnsupportedTerminal messageFor behavior tests, keep using HeadlessBackend on the JVM. Native CI is a packaging
and reachability gate; the headless suite provides fast interaction coverage.
-
Confirm the JVM build and tests first:
./mill app.compile ./mill app.test
-
Build the smallest failing application with
--no-fallbackstill enabled. -
Inspect your dependencies for runtime reflection, JNI, dynamic proxies, resource lookup, or classpath scanning.
-
Prefer direct registration or compile-time derivation over adding broad native configuration.
-
If a third-party library truly needs GraalVM metadata, keep that metadata beside the application and test it in CI.
- build on the same OS/architecture you distribute;
- run
--helpand a headless startup smoke test; - run the binary in a real terminal and verify resize, mouse, paste, and cleanup;
- compare any filesystem/resource lookup with the JVM version;
- preserve license notices for bundled dependencies;
- publish a checksum with downloadable artifacts.
Read Architecture for the compile-time layer and Troubleshooting for failure triage.
Documentation is maintained in website/docs. Read the styled guide · API reference · MIT license