Distributing Java webapps via Docker is pretty widespread. However, regarding replacing desktop applications, it suffers from a not-so-great integration with the user’s desktop. On OSX, a quite popular distribution channel is Homebrew. Let’s dedicate this post to check how to distribute our desktop webapp via Homebrew.
The basics of Homebrew lie in the formula. A formula is a Ruby file describing facts about the app, such as:
- Where to download it
- Its homepage
- Its dependencies
- How to install it
- etc.
The download archive must be in gzipped TAR format (tar.gz
extension).
The first step is to compress the JAR into such an archive.
It can be automated during the Maven build using the Assembly plugin:
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<descriptors>
<descriptor>src/main/assembly.xml</descriptor>
</descriptors>
<tarLongFileMode>posix</tarLongFileMode>
<finalName>renamer-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>tar.gz</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
The assembly configuration itself looks like:
<?xml version="1.0" encoding="UTF-8" ?>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0
http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>archive</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>.</outputDirectory>
<includes>
<include>${project.build.finalName}.${project.packaging}</include>
</includes>
</fileSet>
</fileSets>
</assembly>
Now, every time mvn package
is run, it also creates a tar.gz
archive from the JAR.
The following assumes Homebrew is installed on your system. |
Homebrew makes it easy to create a template formula Just point to the archive location:
brew create file://${HOME}/projects/private/renamer/target/renamer-0.0.1-SNAPSHOT.tar.gz
For now, let’s point to a local file location. |
This will create the /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/renamer.rb
file with the following content:
# Documentation: https://docs.brew.sh/Formula-Cookbook
# http://www.rubydoc.info/github/Homebrew/brew/master/Formula
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
class RenamerApp < Formula
desc ""
homepage ""
url "file://${HOME}/projects/private/renamer/target/renamer-0.0.1-SNAPSHOT.tar.gz"
sha256 "79eecb7973a137c18c81979df816bed6e48366122e4537f6765cea89ebab9110"
# depends_on "cmake" => :build
def install
# ENV.deparallelize # if your formula fails when building in parallel
# Remove unrecognized options if warned by configure
system "./configure", "--disable-debug",
"--disable-dependency-tracking",
"--disable-silent-rules",
"--prefix=#{prefix}"
# system "cmake", ".", *std_cmake_args
system "make", "install" # if this fails, try separate make/make install steps
end
test do
# `test do` will create, run in and delete a temporary directory.
#
# This test will fail and we won't accept that! For Homebrew/homebrew-core
# this will need to be a test that verifies the functionality of the
# software. Run the test with `brew test renamer-app`. Options passed
# to `brew install` such as `--HEAD` also need to be provided to `brew test`.
#
# The installed folder is not in the path, so use the entire path to any
# executables being tested: `system "#{bin}/program", "do", "something"`.
system "false"
end
end
As can be seen, some of the code is about meta-data while the rest is aimed at installation proper.
The first part (i.e. desc
and homepage
) can be easily completed:
class Renamer < Formula
desc 'Example of a webapp desktop integration'
homepage 'https://blog.frankel.ch'
url 'file://${HOME}/projects/private/renamer/target/renamer-0.0.1-SNAPSHOT.tar.gz'
sha256 '58d5fab8292c3fe4b2b97666f20fb4e1fbb4495aa7842594194cd42691f9c43f'
end
As for the second part, the template defaults to compile from sources, which is not relevant to JAR files. In our case, the process should be just to:
- Put the JAR in the correct folder
- And then, create a link to execute it
As I didn’t know how to do that, I had to be creative. I could have ramped up my Ruby skills, read the whole documentation and came back a few weeks later. Or I could just have got inspiration from an existing formula for JAR. Guess what? I found such on in Batik.
It translates quite easily:
bottle :unneeded (1)
depends_on :java => '1.8+' (2)
def install
libexec.install Dir['*'] (3)
bin.write_jar_script libexec/'renamer-0.0.1-SNAPSHOT.jar', 'renamer' (4)
end
1 | Inform that compilation is not needed |
2 | Set a dependency to the Java runtime |
3 | Put the extracted JAR in to the "private" libexec folder |
4 | Create a shell script to launch the JAR in the "public" bin folder |
At this point, the JAR webapp can be launched using the following simple command:
renamer
The best part is that desktop integration is fully supported!
This is only the beginning, as there are further steps to complete the distribution process:
- Store the
tar.gz
archive online e.g. in a Github repository. Change the URL from a local file to the online location inside the formula - Create a script that launches the JAR and open the browser at http://localhost:8080.
Packages it inside the
tar.gz
archive and use it instead of creating the JAR launcher in the formula
Conclusion
Besides Docker, Homebrew is a perfectly valid solution to distribute webapps as JARs. The upside is that desktop integration is complete. On the downside, it’s limited to OSX platforms.