/ WEBAPP, HOMEBREW, SPRING BOOT

Distributing webapps via Homebrew

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:

pom.xml
<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:

src/main/assembly.xml
<?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:

  1. Put the JAR in the correct folder
  2. 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
1Inform that compilation is not needed
2Set a dependency to the Java runtime
3Put the extracted JAR in to the "private" libexec folder
4Create 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.

The complete source code for this post can be found on Github.
Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with narrower interests like Software Quality, Build Processes and Rich Internet Applications. Currently working for Exoscale. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More