The montarre package provides tools for producing platform-specific native executables
and installation media from a single platform-independent source package.
In traditional programming environments, prior to Java, compilers typically produced native code for the platform
upon which they were running. If you were on hardware architecture A, using operating
system O, then unless you were willing to go through the pain and suffering of
configuring a cross-compiler, you could only produce native executables that would run on
hardware architecture A, using operating system O.
As a consequence of this, if you wanted to distribute a program for a range of hardware architectures and
operating systems, you would have to maintain at most M * N systems to build code
(where M is the number of operating systems and
N
is the number of hardware architectures). Worse, all of these systems had to be trusted to actually produce code
that was correct given a single package of source code. Even worse, because each platform would inevitably have
its own set of tools, the singled shared build procedure for the code would become exponentially complicated with
lots of branching logic in order to get it to run correctly on all platforms
.
Java, with varying degrees of success, opted to follow a
write once run anywhere
paradigm: The output of the compiler is platform-neutral bytecode, and end-consumers would simply load this
bytecode into their locally installed Java Virtual Machine. It therefore wouldn't matter
on what hardware architecture and/or on which operating system the code was compiled, as the output of the process
would ostensibly be the same regardless. This would essentially collapse the above diagram to one that looked like
this:
This was theoretically absolutely ideal from a developer perspective. A developer could maintain a single machine
and use it to produce code for any number of completely different platforms. The developer could offer a single
download link online for the program instead of having to offer one per platform.
In practice, there were some issues with this. Using the above system, it is not possible to know ahead of time
which version of the Java VM is installed on end-user systems. Java bytecode is versioned, and a given Java VM has
a stated upper bound on the bytecode versions it can support. If a user has a Java 21 VM installed, and is given
Java bytecode targeting Java 23, the bytecode will simply not run. Worse, on many operating systems, the Java VM
will print an error message about the bytecode version, but the desktop environment will simply hide/discard the
message. This leaves users with silently failing programs.
In Java 14, the
jpackage tool was introduced. The
jpackage
tool can be handed Java bytecode, and a Java VM, and the tool will produce a native executable for the current
platform that runs the given bytecode. This is both a benefit and a curse; a benefit because it is now possible to
package Java code in a manner that makes it immune to the problem of not having a suitable Java VM installed, but
a curse because it could indicate somewhat of a return to the
N by M
compilation model.
Let's consider what might be an ideal build setup that would keep our nice, simple, straightforward
platform-neutral builds, but would also be able to produce platform-specific artifacts on demand without
introducing any complexity into the build.
In an ideal world, we would have our Java compilation process stay completely platform-independent, and produce
identical results regardless of the platform upon which it is run. The output of this process would be a
completely platform-neutral artifact that a user could theoretically run if they had an
appropriate runtime installed. The platform-neutral artifact could be signed, published and that would be the end
of it from the perspective of our build system.
We would then like a second process, completely isolated and decoupled from the first, where that
platform-independent artifact could be downloaded from a repository and transformed into something more familiar
and suitable for the platform upon which it is running. Ideally, the second process would leave the bytecode of
the original platform-independent artifact completely intact so that we could trust that the code is still
"correct" and hasn't been broken in some platform-specific way. The jpackage tool
can achieve this, but requires a lot of per-platform configuration in order to get good results. In practice, the
jpackage tool really requires knowledge of your original build process as it will not
otherwise have all the information it needs to, for example, categorize any platform-dependent native libraries
included in the original artifact, or have access to other metadata such as license information.
The montarre package attempts to provide tools to achieve the above in relatively
painless manner. It attempts to adhere to a paradigm of compile once, transform anywhere!
The montarre package provides the following features: