Packaging a project release (goreleaser part 2)
In the previous post, I used goreleaser to add binaries to a project release. Now let's have goreleaser build a Homebrew formula as well. Automatically, and for macOS and Linux alike.
Convenience comes in packages
There was a tiny problem with the macOS binary in the previous post . As it is not signed by an Apple developer, it get quarantined when downloaded through a browser. So this time let's package the binary for delivery via Homebrew. Using a package manager is more convenient for the users anyway, and they also get updates delivered and can easily uninstall the package if they don't want to use it anymore.
Homebrew
(Mug images licensed under CC-BY the Homebrew project owners.)
macOS users, at least the tech-savvy ones, know and love Homebrew, the Missing Package Manager. Moreover, Homebrew is also available for Linux. And I mention that here because gorleeaser can build Homebrew formulae for both macOS and Linux.
Homebrew has a core repository of packages but also supports providing your own “
tap” for dispensing your freshly brewed binary, which is what I will be using here. (If you really want your project listed in the Homebrew core repository, you can do so, but the
process is quite involved, and goreleaser
also does not generate valid homebrew-core
formulas at the moment.)
Let's start brewing
To create a formula, I add a brews
section to .goreleaser.yml
. Note the plural – it is possible to generate multiple formulae.
I am going to create the most minimal config possible. (After all, I don't want to replicate the manual but only demonstrate you what is possible and how to get up and running in the easiest way.) That means I rely on default values wherever possible. For example, the recipe name template defaults to the project name, which should be fine in most cases.
You can add customization where necessary, as listed in the
Homebrew section of the goreleaser
docs.
The first necessary entries are the name and owner of the GitHub or GitLab repository that shall serve as your Homebrew tap. Yes, a tap is nothing else but a GitHub or GitLab repo. A Homebrew user just needs to tell their Homebrew client the name of your tap, and then they can immediately install and upgrade formulae from that tap.
brews:
-
tap:
owner: appliedgo
name: homebrew-tools
With these settings, goreleaser
generates and publishes a formula to a repository named “appliedgo/homebrew-tools”. The name of the tap itself is the same minus the “homebrew-” part: “appliedgo/tools”. (Side note: The goman
repo is in my appliedgocode
organization, but for a nicer tap name, I decided to place the Homebrew formulae repository in my appliedgo
organization.
If I wanted to have a tap for goman
only, I could have named the repo “homebrew-goman”. However, as I plan to provide more tools from the same tap, I chose the quite generic name “tools” here.
Also, I want to avoid unnecessary repetition:brew install appliedgo/tools/goman
sounds better thanbrew install appliedgo/goman/goman
.
But that's just a matter of taste. I have come across quite a few such taps with the same name for tap and tool, and that's ok. We're not in a style contest here, are we?
The next entry without useful default values is the Git author who commits to the repository. Add your GitHub/GitLab username and email here. I added mine:
commit_author:
name: christophberger
email: [email protected]
Together with the GITHUB_TOKEN that I already created in the previous post, goreleaser
now can publish the formula to the tap repo under my name.
The following values are not strictly necessary but I would highly recommend setting them. Especially if your tool has some caveats, ensure to list them in the caveats entry. Homebrew displays this entry after installing the tool.
For the license name, ensure to use the SPDX identifier of your license.
homepage: "https://github.com/appliedgocode/goman"
description: "The missing man pages for Go binaries"
license: "BSD-3-Clause"
caveats: "Returns strange results at full moon"
The following entry controls when to upload (commit) the formula to the Homebrew tap. Default is false
, which means the formula is always uploaded to the tap. Use true
to always skip the upload, and auto
for skipping the upload if the latest Git tag contains an indicator for a pre-release (like, for example, v1.1.0-beta1). I am using auto
here, as I don't want to pester all goman
users with pre-release updates unnecessarily.
skip_upload: auto
If your tool has dependencies on other tools that are available via Homebrew, you can add a dependencies
subsection. Homebrew will then install the dependencies as well. And if there are any Homebrew packages that are in conflict with yours, ensure to list them in conflicts
. Homebrew warns the user if any of these conflicting packages is installed already.
# just examples, not required for goman:
dependencies:
- name: git
- name: zsh
type: optional
conflicts:
- svn
- bash
As goman
has neither any dependencies nor any known conflicts, I omit both.
So that's my complete but fairly minimal brews
section in .goreleaser.yml
:
brews:
- tap:
owner: appliedgo
name: homebrew-tools
commit_author:
name: christophberger
email: [email protected]
homepage: "https://github.com/appliedgocode/goman"
description: "The missing man pages for Go binaries"
license: "BSD-3-Clause"
skip_upload: auto
(Yes, the caveat is not included. It does not apply to goman
, or so I hope!)
And now: release!
To prepare the release, I only need two last steps.
- I create my new Homebrew tap at github.com/appliedgo/homebrew-tools.
- I create and push a new Git tag
Now here comes the magic: I just need to push a new Git tag and run
goreleaser release --rm-dist
and goreleaser
builds and uploads a goman
formula to my tap repository. Now it is ready for installing!
Soooo convenient.
I now just need to switch my brains from “tool publisher” to “Homebrew user” and install goman
.
brew tap appliedgo/tools
brew install goman
Worked! Awesome!
With Homebrew, your releases already cover macOS, Linux, and even Windows to some extent, via Windows Subsystem for Linux (WSL).
But you can get even more specific and create pure Linux packages (.deb, .rpm, .apk, or a Snapcraft snap), pure Windows packages (for Scoop), and even a Docker image. And more. I leave this as a homework exercise.
Happy coding!