Spread The Words: Distributing a Man Page With Goreleaser And Homebrew

In the previous Spotlight, I discussed a few options for writing or generating a man page for a CLI app. That's fine for having one locally, but how to distribute this man page to your app's users?

If you plan to distribute your CLI tool via go install, you can have your binary install the man page on demand:

  1. Use a //go:embed directive to embed the man page in the binary
  2. Add a flag to the tool that copies the man page to /usr/local/share/man/man1/ and ensures that the file mode is 0644.

A nobler way of distributing an app is through package managers. There is a plethora of them, but luckily, there is also goreleaser, a tool that builds releases of your CLI app and manages various package managers for you.

I wrote an introduction to goreleaser on my blog, and a second part where I add Homebrew to my goreleaser config. In these articles, I used the tool goman that I wrote to fake man pages by grabbing the command's README instead. Now I am going to make goman obsolete by telling everyone how to package real man pages alongside their tools! 😉

I won't go through all the steps of the two goreleaser articles again; please read the articles if you're new to either of goreleaser, Homebrew, or goreleaser’s Homebrew integration.

In a nutshell, the goreleaser config I built in the second article creates a new Homebrew tap called appliedgo/tools, so that Homebrew users can install goman as brew install appliedgo/tools/goman.

Adding a man page to the Hombrew formula

The goal for today is to modify the goreleaser config file to build the man page and have Homebrew install the man page along with the binary.

This turned out to be surprisingly easy.

Step 1: Build the man page

I decided to write the man page for goman in Markdown and have pandoc turn this into a man page.

goreleaser has a pre-build hook that I utilized for running pandoc. The original pre-build hook looked like this:

before:
  hooks:
    - go mod tidy
    - go mod download

The before.hooks property runs commands before the build, and I use this to tidy the dependency file and download missing ones.

For generating the man page, I only had to add this line to the list in order to invoke pandoc:

    - pandoc -s -t man goman.1.md -o goman.1

Step 2: Add the generated man page to the archive files

goreleaser builds binaries for several OS/architecture combinations (based on what I told it). In the archives.files property, I tell goreleaser which files to zip up with the binary for getting released on GitHub:

archives:
    files:
      - README.md
      - LICENSE*

Here, I added the generated man page:

      - goman.1

Step 3: Ask Homebrew to install the man page

Finally, Homebrew must know that there is a man page to install. Homebrew makes installing man pages dead easy by providing a man1.install directive that I added to goreleaser’s brews.extra_install property. (The extra_install property lets me add installation steps without overriding the whole installation procedure.)

brews:
  - repository:
      owner: appliedgo
      name: homebrew-tools
# ...omitted for brevity...
    extra_install: |
		man1.install "goman.1"

(Fun fact: Homebrew formulas are Ruby code, and if you don't put the man page file in quotes, Ruby thinks goman.1 is a floating-point number and complains loudly.)

Wrapping all up

Now I only had to create a new Git tag, push the changes to the remote repo, and run goreleaser release.

The goman.1 file is now included in all .gz files of the release, and the Homebrew formula will install it to the appropriate man1 directory.

Now goman behaves a bit more like a well-mannered Linux/macOS CLI tool. What's your next tool to equip with a man page?