I recently packaged a program I wrote called notes for distribution via Homebrew, following this excellent tutorial.

To make your package installable via Homebrew, you put it in a GitHub repo and create a release. Then you create a tap, a repo containing at least one formula. The formula is a Ruby file pointing to a downloadable tarball of the package, with instructions on how to build and install it.

When a user downloads your package, the tarball is saved to /Library/Caches/Homebrew. It gets unzipped and, depending on the formula, parts of the package are copied to /usr/local/Cellar/<pkg>/<version>. This directory, in the user’s cellar, is called a keg.

To make writing formulae easier, you can use variables for directory locations. For example, bin.install foo will create /usr/local/Cellar/<pkg>/<version>/bin/foo, use chmod to make sure foo is executable, and create a symlink from /usr/local/bin/foo to foo in the cellar.

They say the right example is worth 1000 lines of documentation. Enough beating around the bush — here is notes’ directory tree, courtesy of tree:

├── README.md
├── _completions
│   ├── c.bash
│   ├── c.zsh
│   └── init.sh
├── _config
│   └── env.sh
├── _helpers
│   └── helpers.sh
└── bin
    └── notes

And here’s the formula for installing notes, i.e. building the notes keg:

class Notes < Formula
  desc "..."
  homepage "https://github.com/kylebebak/notes"
  url "https://github.com/kylebebak/notes/archive/1.0.0.tarball.gz"
  version "1.0.0"
  sha256 "e17405adc655824dec3ca6e2a9a4b199a715743ed5f0948df58f6bb369267aa3"

  def install
    bin.install "bin/notes"
    prefix.install Dir["_completions"]
    prefix.install Dir["_helpers"]
    prefix.install Dir["_config"]

This install method creates a keg with the same directory tree as the one in source code, while ignoring metadata like README.md and LICENSE, and tests or CI scripts, if I had any. The prefix.install method copies the directories into the cellar without polluting the executable namespace under /usr/local/bin. The only symlink created by this formula is /usr/local/bin/notes.

Once you have your tarball release and your tap on GitHub, users can install your program with two shell commands:

brew tap kylebebak/notes
brew install notes

Tab completion is crucial to notes’ usability. Enabling it was the trickiest part of writing notes; I explain how in this post.