How to Distribute Binaries for OS X Using Homebrew

git diff is near and dear to my heart, so when a post on better diffs showed up on Hacker News last week, it immediately caught my attention. It's given me a few ideas on how to improve delta, particularly its command-line output.

But then in the comments I saw this from paulirish:

A lot of people below are asking why a bash script (that depends on a perl script) is being recommended to install via NPM? The short reason is that NPM is the most straightforward way to get a script installed as a global binary in a cross-platform manner. This approach has worked quite well with git-open. Asking all users to deal with the PATH is not my ideal.

In addition, I wanted a reasonable upgrade path, in case there are neccessary bugfixes. It's not a great experience if users identify bugs but the fix means they manually find it/download/PATH-ify each time. :/

Cross-platform app distribution is a hard problem! Most apps today rely on one language's package manager or another for that: e.g. supervisor and pip, sass and rubygems, diff-so-fancy and npm.

At least on OS X, things are a little easier with Homebrew. It's a great way to distribute binaries, especially with taps giving you full control over your release. Taps allow you to self-manage your release using your own GitHub repo, while Homebrew does the heavy lifting of fetching and installing it to your users' systems. It solves the problem of distributing apps for OS X.

These instructions will show you how to set up a simple tap for your app.

How to Set Up a Homebrew Tap

  1. Create a tap repo on GitHub. The repo should be named homebrew-{mytapname}, where {mytapname} is the name you want to give your tap (which will contain all your Homebrew formulae). I call mine tools.

  2. Tar and upload your binary somewhere. Personally I like GitHub Releases, for its proximity to my source code. A good naming convention for your tar file is {appname}-{version}.tar.gz, e.g. delta-0.5.0.tar.gz. This allows Homebrew to infer your app's version. Example of a simple tar command:

    tar -czf delta-0.5.0.tar.gz delta
    
  3. Generate a SHA-256 hash for your tar file:

    shasum -a 256 delta-0.5.0.tar.gz
    
  4. In your tap repo, create a Formula for your app, using the url and SHA from above. This is a ruby file of the format {appname}.rb. A minimal formula, such as my delta formula, would look roughly like the following (url and sha omitted for brevity):

    class Delta < Formula
      desc "diff tool with browser-based GUI"
      homepage "https://github.com/octavore/delta"
      url "(url omitted)"
      sha256 "(sha from step 3)"
    
      bottle :unneeded
    
      def install
        bin.install "delta"
      end
    
      test do
        system "#{bin}/delta", "--version"
      end
    end
    

    Don't forget to also check out the Formula API for all the things you can do with your formula, like a post_install command.

  5. Commit and push your tap repo to GitHub.

That's it! Now your users can install your app easily with Homebrew using the following command:

brew install {github-user}/{mytapname}/{formula}

For example, delta which is in my github.com/octavore/homebrew-tools repo under delta.rb can be installed as follows:

brew install octavore/tools/delta

To update your app to new versions, simply update the URL and SHA in your formula accordingly, and push the changes to GitHub.