How to create a Homebrew installer
Tags: kotlin, java, homebrew, install
Published: 2021-01-18 05:02:58

 
This post discusses how to create a **homebrew** installer to package any script, application or tool, in this case a Kotlin (Java) based application. This was done for a Kotlin framework called [Slate Kit](https://www.slatekit.com), and here on [Git](https://github.com/slatekit/slatekit).

Building scripts, applications and tools is a part of software engineering and a pretty common task. This process though typically involves a runnable application, installing, uninstalling, upgrading and packaging some dependencies/resources. This can be trivial or somewhat complex. Fortunately, there are package managers like **homebrew** that handle most of the boilerplate. While homebrew is great, and easy to use as an end-user who installs software, I found the documentation and guides on creating an installer somewhat difficult to navigate.

# Terms
Before we get into the details of coding an installer, we need to first understand some terms and concepts. Most of these are represented here at [Homebrew Terms](https://docs.brew.sh/Formula-Cookbook#homebrew-terminology). However, I want to elaborate on a few that are relevant for this post. These are also represented in the diagram below

- **Package**: A package is not listed in the homebrew terminology section but it is basically the **script, application or tool** that you want to install and use. This contains your executable and all the resources.
- **Formula**: A formula is a **Ruby installation script** that defines and installs your package on Mac OS. Since homebrew is Ruby based, the formula is a Ruby script and it typically just moves your package executable and resources into the various install directories.
- **Tap**    : A tap is a git repository containing 1 or more Formulas. You direct homebrew to load your tap(git repo) as the source for your formulas. A tap must be prefixed with `homebrew-`.
- **Version**: The version number of your package to be installed, e.g. `v1.38.0`

# Diagram
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f333h2e6sc0umhdcntcy.png)

# Example
This is a quick example of the entire process of setting up your tap, installing your package and then using your package. In this case the tap is `slatekit/slatekit` ( homebrew automatically interprets this using the `homebrew-` prefix, so this actually is `slatekit/homebrew-slatekit`, the formula is also called `slatekit` ( slatekit.rb ) and finally the executable script is **slatekit** ( a bash script that runs the Kotlin application ). 
```bash
# Set tap (shortcut to github.com/slatekit/homebrew-slatekit)
:> brew tap slatekit/slatekit

# Install formula slatekit.rb to install the package 
:> brew install slatekit 

# Finally use the package ( slatekit script )
:> slatekit new app -name="Sample1" -packageName="mycompany.apps"
```

# Step 1: Package
You need to first create your git repo for the actual package that you plan to install and use. Its libraries, dependencies, and resources need to be stored in one git repo. In this case its [Slate Kit CLI](https://github.com/slatekit/slatekit-cli). There is a file called **slatekit** and this represents a shell script that is the actual tool that will be used. You also need to create a release of this package so there is a zip/tar containing all items in the repo, for example [v1.34.5](https://github.com/slatekit/slatekit-cli/releases/tag/v1.34.5). Since this is a Kotlin/Java based application, the repo contains the libraries( jars ) and some resources, and an executable. This Kotlin/Java application was originally built using gradle `'application'` plugin and running `gradle distZip` to package the slatekit shell script all the libraries.

# Step 2: Tap
You need to create another git repo containing the **homebrew formula** that will perform the install. The tap can contain 1 or more formulas. In this example the **tap** is [slatekit/homebrew-slatekit](https://github.com/slatekit/homebrew-slatekit). This tap provides homebrew a place to look for formulas. Some formulas are submitted to Homebrew for proper approval and available without requiring a tap ( later step ), but this example uses a custom tap so that you don't need to go through the approval process.
**NOTE**: The name of the git repo must start with `homebrew-`

# Step 3: Formula
Now you need to create a formula that defines your package and performs the installation steps. A formula contains a reference to the url/version of the package above, so brew can download the package and unzip it so the formula can install the contents. The **formula** in this example is [slatekit.rb](https://github.com/slatekit/homebrew-slatekit/blob/main/slatekit.rb). The most relevant things in the formula are the url, sha, and the steps in the **install** method. 
```ruby
class Slatekit < Formula
  url "https://github.com/slatekit/slatekit-cli/archive/v1.34.5.tar.gz"
  sha256 "e95375f92a8c0e86082d9b22e11bd6a414ee0de9df77dda84d6bb6dc21061647"
  
  # ...
  
  def install
    bin.install 'slatekit'
    prefix.install Dir["lib"]
    prefix.install Dir["conf"]
    prefix.install Dir["templates"]
    puts "Completed install"
  end
end
```
There are 2 ways to get the sha for the package url, e.g. the `e95375f92a8c0e86082d9b22e11bd6a414ee0de9df77dda84d6bb6dc21061647` in the forumla above. 
### Option 1
```bash
curl -L https://github.com/slatekit/slatekit- cli/archive/v1.34.5.tar.gz > 1.34.5.tar.gz
shasum -a 256 1.34.5.tar.gz
```

### Option 2
Run the brew create command below and the script is created in /usr/local/Homebrew/Library/taps/homebrew/homebrew-core/Formula/slatekit-cli.rb which will have the sha.
```bash
brew create https://github.com/slatekit/slatekit- cli/archive/v1.34.5.tar.gz
```

# Process
There a few important things regarding whats happening in the install step. This is where you specify what to install from the package version that was downloaded.
- **version** since homebrew provides versioning of packages, this package directory will correspond to the version in the formula e.g. [v1.34.5](https://github.com/slatekit/slatekit-cli/releases/tag/v1.34.5) and installed to `/usr/local/Cellar/slatkit/1.34.5`
- **bin.install**: This call will move the **slatekit** shell script to the bin directory `/usr/local/Cellar/slatkit/1.34.5/bin`, make it executable, and available on the command line to call.
- **prefix.install** This call will install the directory from the unzipped package into the folder for this version. e.g. the `lib` directory from package is moved to  `/usr/local/Cellar/slatkit/1.34.5/lib`


# Install
Finally, once you have all this set up, you can simply run the following commands to install the tap and package. 
```bash
brew tap slatekit/slatekit
brew install slatekit
```
The first line `brew tap slatekit/slatekit` is really a call to download the tap and formula from https://github.com/slatekit/homebrew-slatekit. The second call `brew install slatekit` is call to run the formula [slatekit.rb](https://github.com/slatekit/homebrew-slatekit/blob/main/slatekit.rb)
**NOTE**:
You may experience a few issues during install 
- [Homebrew slow install](https://discussions.apple.com/thread/251258165)
- [Homebrew issues on Mac OS Catalina](https://discourse.brew.sh/t/brew-install-very-slow-pauses-for-long-period-while-executing-usr-bin-sandbox-exec-in-post-install/7423)

# Execute
Now with the package installed, you can immediately start using it. In this case, [slatekit](https://github.com/slatekit/slatekit) generates new projects. So we can use it like this :
```bash
slatekit new app -name="MyApp1" -packageName="company1.apps"
slatekit new api -name="MyAPI1" -packageName="company1.apis"
slatekit new job -name="MyJob1" -packageName="company1.jobs"
slatekit new cli -name="MyCLI1" -packageName="company1.apps"
```

# Uninstall
If you want to uninstall everything, you can do 
```bash
brew uninstall slatekit
brew untap slatekit/slatekit
```

# Upgrade
Lastly, if you want to upgrade your package to a new version, then you simply have to publish a new version of your package, change the url, sha in your formula and then run the following:
```bash
brew upgrade slatekit
```

# Conclusion
Well, thats finally it. I created a homebrew installer to allow for a command line tool to generate [slatekit](https://github.com/slatekit/slatekit) projects making it very easy for new users to get started quickly. It sounds like quite a lot at first, but there are only 3 concepts, the package( your script, app, tool), the tap (git repo for formulas), and the formula (installer). Hope this helps!