Jaunch: Launch Programs Your Way!β’
Jaunch is a native launcher for applications that run inside non-native runtimes, including the Java Virtual Machine (JVM), the Python interpreter, or both.
(These instructions are temporary until Jaunch has a stable release.)
Build from source by running make dist
.
Copy the dist/launcher
/dist\launcher.exe
native launcher to your
application distribution root, renaming it to match your desired naming.
For example, if your application is called Fizzbuzz, you might name it
fizzbuzz
/fizzbuzz.exe
.
Create a directory beneath your application root, into which your application's
Jaunch TOML configuration will be stored. This configuration directory can be
named jaunch
, .jaunch
, config/jaunch
, or .config/jaunch
, depending on
your requirements and taste. Or you can customize the allowed configuration
directory names by editing the JAUNCH_SEARCH_PATHS
list in
jaunch.c and matching configDirs
list in
main.kt.
Copy the TOML files from the dist/jaunch
folder to your application
distribution's configuration folder, as created in (3).
Rename the launcher.toml
file to match your launcher name from (2)
(e.g. fizzbuzz.toml
).
Edit the renamed TOML file's contents to set the parameters for your
application. See the comments in common.toml
for detailed guidance.
If you don't need the JVM, you can remove the JVM-specific parts.
If you don't need Python, you can remove the Python-specific parts.
See BUILD.md.
To maximize the flexibility of downstream projects to use and adapt this code, Jaunch is Unlicensed. See the UNLICENSE file for details.
Do you have a desktop application that runs in the Python interpreter or Java Virtual Machine? You probably want a friendly native launcher, for example a .exe file in the case of Windows, that launches your program, right?
There are many existing ways for both Python and the JVM to achieve this goal; see Alternatives below. But none of them do what Jaunch does:
Jaunch began as a tool to launch Fiji, a rather complex application for the JVM. But Fiji also needed the ability to be launched via Python (i.e. start Python which then starts Java using JPype), to make its in-process Python integration as convenient as possible for users to access. So we figured hey, Jaunch knows how to link to libpython now, so why not support standalone Python apps as well? We do not know of an existing general tool in the Python ecosystem that fills this niche of launching Python without necessarily bundling Python.
Jaunch consists of two parts:
A native launcher, written in C, kept reasonably minimal.
A "configurator" executable written in Kotlin Native, which does the heavy lifting.
The native launcher (1) will be named after your particular application, it is placed in
the base directory of your application. For example, for an application called Fizzbuzz,
the launcher could be named fizzbuzz.exe
.
The configurator (2) is named jaunch-<os>-<arch>.exe
, and placed in the jaunch
subdirectory of your application. Examples: for ARM64 Linux it would be named
jaunch/jaunch-linux-arm64
, whereas for x86-64 Windows it would be named
jaunch/jaunch-windows-x64.exe
. The reason for the <os>-<arch>
suffix is so that
portable applications can ship with all needed jaunch configurator executables in
the same jaunch
folder, without any name clashes.
The native launcher invokes the configurator as a subprocess, passing its entire argv
list to the appropriate jaunch
program via a pipe to stdin. The jaunch configurator is
then responsible for outputting the following things via its stdout:
JVM
to launch a JVM program using JNI functions (e.g. JNI_CreateJavaVM
).PYTHON
to launch a Python program using Python's Stable ABI (e.g. Py_BytesMain
).ABORT
to launch nothing.TODO: Update this explanation. It's different per directive now.
To deliver this output, the configurator must do the following things:
If your application's needs along these lines are relatively minimal—e.g. if you bundle a JDK in a known location, and always pass all user arguments as main arguments—you would likely not even need Jaunch's Kotlin/configurator code at all; you could write your own simple jaunch configurator as a shell script and/or batch file.
However, Jaunch was designed to satisfy the needs of applications with more complex command line functionality, which is where the Kotlin configurator comes in. It reads declarative TOML configuration files, which define how Jaunch will make the above decisions. The TOML configuration is its own layer of the architecture, which is best learned by reading the common.toml file directly. With this design, the behavior of Jaunch can be heavily customized without needing to modify source code and rebuild. And for applications that need it, there can be multiple different native launchers in the application base directory that all share the same jaunch configurator native binaries with different TOML configurations.
As so often in technology, there are so many. And yet nothing that does what this program does!
libs
folder).-Xmx5g
to override max heap size).You can use jpackage to generate one for every platform you want to support: typically Linux, macOS, and Windows. The jpackage tool is part of the Java Development Kit (JDK), so you might think there is no need for another Java native launcher. But jpackage is inflexible:
The jpackage tool mandates a specific directory structure for your application,
e.g. putting all JAR libraries in the lib/app
folder, and the bundled Java
runtime into lib/runtime
. As far as I know, you can't use a different structure.
The native launchers generated by jpackage do not allow users to pass custom arguments
to the JVM ("users can't provide JVM options to the application"). All arguments
are handed verbatim to your Java application's main method. So if your users want to
e.g. increase the maximum heap size by passing -Xmx20g, they must edit the jpackage
.cfg file located in the application's /app
directory.
The jpackage tool also provides no way to support options like --debugger=8000
which are transformed into JVM arguments like
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=localhost:8000
.
To simulate that sort of thing with jpackage, you'd need to implement an option in
your application's interface like "restart application in debug mode" with a port
and a "suspend" checkbox. And you'd have to decide whether to reset that every time
your application launches, or else require the user to go back into the options and
disable the debug mode again for next time when they are finished.
jpackage apparently no longer supports generating an application without a bundled Java. It did once, back when it was the "JavaFX packager", but based on Internet searches and the docs, it seems they removed this feature. So you cannot have a "portable application" version of your program, runnable from a USB stick across multiple platforms, leveraging the current computer's system installation of Java.
jpackage has a feature to generate multiple launchers, each with its own arguments and
main class, which is nice. But they end up as separate executables, so you cannot have
an option like fizzbuzz --update
that runs a different main class. (It would be
doable on the Java side by having a main class that always gets run, which then
delegates to another main class, but it's a hassle.) Whereas the design of Jaunch is
flexible enough to support a --main-class
option so that fizzbuzz --main-class org.example.AltClass
will run AltClass
rather than FizzBuzz's default main class.
On Windows, a jpackage launcher must be either a console application, or a non-console application. It cannot be both, contingent on how the user invokes it. Whereas Jaunch connects to the existing console when there is one, but does not create a new one.
All of that said, jpackage is a very nice tool, and if it works for you, use it! But if you want more flexibility—if you want to launch Java Your Way—then try Jaunch.
java
in its own separate processE.g., via shell scripts such as Contents/MacOS/JavaApplicationStub
.
PATH
, and/or pointed at by JAVA_HOME
, and/or known to /usr/libexec/java_home
on macOS, /usr/bin/update-java-alternatives
on Linux, etc. In a nutshell: you are doing your own discovery of OpenJDK installations.java
on the system path is used.java
given by /usr/libexec/java_home -v 1.8
is used.E.g. SDKMAN!, cjdk, install-jdk, jgo.
install4j by ej Technologies.
-J
argument prefix..l4j.ini
file.dlopen
/dlsym
, but rather links to libjvm. See my post on Kotlin Discuss.execvp
, either itself with changes to environment variables, or the system Java.java
in a separate process.Why (AstroImageJ)
Cargo.toml
and file_handler.rs
in order to compile it for Linux.From the PyInstaller website:
PyInstaller bundles a Python application and all its dependencies into a single package. The user can run the packaged app without installing a Python interpreter or any modules.
From the Briefcase website:
Briefcase is a tool for converting a Python project into a standalone native application."
From the constructor website:
constructor
is a tool which allows constructing an installer for a collection of conda packages.