Homebrew
Distribute a Go CLI through a Homebrew tap. Pilum cross-compiles the binary for four platforms, packages and checksums the archives, generates the formula and pushes it to your tap repository.
Pilum does not create GitHub Releases or upload the archives. The formula points at <project_url>/releases/download/<tag>/..., so the archives must be uploaded to that release before users run brew install (see Release Workflow).
Prerequisites
- Go installed (the build is a Go cross-compile)
- git installed
- A Homebrew tap repository, e.g.
my-org/homebrew-tap - A GitHub token that can push to the tap repository
The gh CLI is not required. Pilum clones and pushes the tap with plain git over HTTPS, using the token from the environment variable named in homebrew.token_env.
Service Configuration
name: myclitype: homebrewdescription: My CLI toollicense: MIT
build: language: go version_var: github.com/my-org/mycli/cmd.version
homebrew: project_url: https://github.com/my-org/mycli tap_url: https://github.com/my-org/homebrew-tap token_env: GH_TOKENUse type: homebrew (or provider: homebrew).
Required Fields
| Field | Description |
|---|---|
name | Binary name and formula name |
description | Formula desc |
license | SPDX license identifier (MIT, Apache-2.0, …) |
homebrew.project_url | Repository URL where the release archives are hosted, e.g. https://github.com/my-org/mycli |
homebrew.tap_url | HTTPS URL of the tap repository |
homebrew.token_env | Name of the environment variable holding a token that can push to the tap |
Build Settings
The build is a fixed Go cross-compile. These build keys apply:
| Field | Type | Description |
|---|---|---|
build.version_var | string | Go variable to set to the tag, via -ldflags "-X <version_var>=<tag>" |
build.cmd, build.flags, build.output and build.platforms are ignored by this recipe. project is not used. build.env_vars are exported into the step’s environment (useful for GOFLAGS or GOPRIVATE), but CGO_ENABLED is always 0.
The build runs go build . from the directory you run pilum in, so the main package must be there (normally the repository root). Each binary is built with -ldflags="-s -w".
Deployment Steps
The Homebrew recipe runs:
- build binaries [build] —
go buildfor darwin/amd64, darwin/arm64, linux/amd64 and linux/arm64 intodist/<name>_<tag>_<os>_<arch>. - create archives [build] — Packs each binary, renamed to
<name>, intodist/<name>_<tag>_<os>_<arch>.tar.gz. - generate checksums [build] —
shasum -a 256of the archives intodist/checksums.txt. - update formula [deploy] — Writes
dist/<name>.rbusing the checksums. - push to tap [deploy] — Clones the tap, copies the formula to
Formula/<name>.rb, and commits and pushes aspilum[bot]with the messageUpdate <name> to <tag>.
Always pass --tag. The tag is part of every file name and of the release URL; without it the names come out empty.
Generated Formula
For --tag=v1.0.0, Pilum writes dist/mycli.rb:
class Mycli < Formula desc "My CLI tool" homepage "https://github.com/my-org/mycli" version "1.0.0" license "MIT"
on_macos do if Hardware::CPU.arm? url "https://github.com/my-org/mycli/releases/download/v1.0.0/mycli_v1.0.0_darwin_arm64.tar.gz" sha256 "..." else url "https://github.com/my-org/mycli/releases/download/v1.0.0/mycli_v1.0.0_darwin_amd64.tar.gz" sha256 "..." end end
on_linux do if Hardware::CPU.arm? url "https://github.com/my-org/mycli/releases/download/v1.0.0/mycli_v1.0.0_linux_arm64.tar.gz" sha256 "..." else url "https://github.com/my-org/mycli/releases/download/v1.0.0/mycli_v1.0.0_linux_amd64.tar.gz" sha256 "..." end end
def install bin.install "mycli" end
test do system "#{bin}/mycli", "--version" endendversion is the tag without a leading v. The release download path uses the tag as given.
Release Workflow
Split the pipeline around the release upload. Pilum’s own release workflow follows this order:
# 1. Build, archive and checksum (build-tagged steps only)pilum build mycli --tag=v1.0.0
# 2. Create the GitHub Release for v1.0.0 and upload dist/*.tar.gz and dist/checksums.txtgh release create v1.0.0 dist/*.tar.gz dist/checksums.txt --generate-notes
# 3. Generate the formula and push it to the tap (deploy-tagged steps only)GH_TOKEN=... pilum deploy mycli --only-tags=deploy --tag=v1.0.0Step 3 reads dist/checksums.txt, so it must run in the same checkout as step 1. A plain pilum deploy --tag=v1.0.0 runs all five steps in one go, which suits a release that is created before Pilum runs, but users cannot install until the archives are uploaded.
GitHub Token
The token needs write access to the tap repository:
- Classic token:
reposcope (orpublic_repofor a public tap). - Fine-grained token: Contents read/write on the tap repository.
export GH_TOKEN="ghp_xxxxx"In GitHub Actions the default GITHUB_TOKEN cannot push to another repository, so store a separate token as a secret and expose it under the name set in token_env.
Example Commands
# Build binaries, archives and checksums onlypilum build mycli --tag=v1.0.0
# Full pipeline: build, formula, push to tappilum deploy mycli --tag=v1.0.0
# Preview commandspilum dry-run mycli --tag=v1.0.0Multi-Platform Builds
| Platform | Architecture | Archive |
|---|---|---|
| macOS | Intel (amd64) | mycli_v1.0.0_darwin_amd64.tar.gz |
| macOS | Apple Silicon (arm64) | mycli_v1.0.0_darwin_arm64.tar.gz |
| Linux | x86_64 (amd64) | mycli_v1.0.0_linux_amd64.tar.gz |
| Linux | arm64 | mycli_v1.0.0_linux_arm64.tar.gz |
The platform list is fixed.
Troubleshooting
Formula push fails
Error: GH_TOKEN environment variable is not setExport the variable named in homebrew.token_env. If it is set but the push is rejected, the token lacks write access to the tap. tap_url must be an https:// URL, because the token is inserted into it for the clone.
brew install returns 404
The formula points at a release asset that does not exist. Upload dist/*.tar.gz to the GitHub Release whose tag matches --tag exactly.
Checksum mismatch
The archives in the release differ from the ones the formula was generated from, usually because they were rebuilt after update formula ran. Upload the archives and run the deploy steps from the same dist/ directory.
Binary not found
After pilum build, dist/ should contain the four .tar.gz archives and checksums.txt:
ls dist/# checksums.txt# mycli_v1.0.0_darwin_amd64.tar.gz# mycli_v1.0.0_darwin_arm64.tar.gz# mycli_v1.0.0_linux_amd64.tar.gz# mycli_v1.0.0_linux_arm64.tar.gzIf the build step failed, check that the main package is in the directory you ran pilum from.
Installation by Users
Once the release assets are uploaded and the formula is in the tap:
brew tap my-org/tapbrew install mycliOr in one command:
brew install my-org/tap/mycliNext Steps
- Service Configuration — Full
pilum.yamlreference - Build Configuration —
version_varand other build settings - Environments and CI — Running Pilum in CI
- Troubleshooting — Common issues