homebrew-dev

SKILL.md

Homebrew Packaging Skill

You are an expert at packaging software with Homebrew — both formulas (source builds and pre-built binaries) and casks (macOS apps, fonts, arbitrary files). You help users create, test, and distribute packages through personal taps on GitHub.

Deciding: Formula vs. Cask

Scenario Use
CLI tool with source code (C, Go, Rust, etc.) Formula
Pre-built binary tarball for a CLI tool Formula (no build step)
macOS .app bundle Cask
Font files (.ttf, .otf) Cask
PKG installer Cask
Arbitrary file placement (e.g. completions, configs) Cask with artifact
App with auto-updater Cask with auto_updates true
Both a CLI and a GUI component Both (linked from cask with binary)

Creating a Formula

Scaffold

brew create https://example.com/mytool-1.0.tar.gz

This auto-populates url, sha256, version, and opens the editor.

Key fields

class Mytool < Formula
  desc "One-line description (imperative, no trailing period, no 'A' prefix)"
  homepage "https://example.com/mytool"
  url "https://example.com/mytool-1.0.tar.gz"
  sha256 "abc123..."      # from: shasum -a 256 <file>
  license "MIT"
  version "1.0"           # omit if parseable from url

  depends_on "cmake" => :build           # build-time only
  depends_on "openssl@3"                 # runtime
  depends_on "python@3.12" => :optional  # optional runtime
  depends_on :macos => :monterey         # minimum macOS

  def install
    system "./configure", *std_configure_args
    system "make", "install"
  end

  test do
    system "#{bin}/mytool", "--version"
    assert_match "1.0", shell_output("#{bin}/mytool --version")
  end
end

Install directory variables

Variable Expands to
bin $(brew --prefix)/bin
lib $(brew --prefix)/lib
include $(brew --prefix)/include
share $(brew --prefix)/share
etc $(brew --prefix)/etc
var $(brew --prefix)/var
libexec $(brew --prefix)/opt/<name>/libexec

Build systems

# Autoconf/Make
system "./configure", *std_configure_args
system "make", "install"

# CMake
system "cmake", "-S", ".", "-B", "build", *std_cmake_args
system "cmake", "--build", "build"
system "cmake", "--install", "build"

# Cargo (Rust)
system "cargo", "install", *std_cargo_args

# Go
system "go", "build", *std_go_args, "-o", bin/"mytool", "./cmd/mytool"

# Pre-built binary (no build)
def install
  bin.install "mytool"
  man1.install "mytool.1"
end

Architecture conditionals

on_arm do
  url "https://example.com/mytool-1.0-arm64.tar.gz"
  sha256 "arm_sha..."
end

on_intel do
  url "https://example.com/mytool-1.0-x86_64.tar.gz"
  sha256 "intel_sha..."
end

Creating a Cask

Scaffold

brew create --cask https://example.com/MyApp-1.0.dmg

Artifact types and their uses

Artifact Use case Installs to
app "MyApp.app" macOS .app bundles /Applications
font "MyFont.ttf" Font files ~/Library/Fonts/
binary "mytool" Pre-built CLI tool from archive $(brew --prefix)/bin/
artifact "myfile", target: "~/.config/myfile" Arbitrary file placement Custom path
pkg "MyApp.pkg" PKG installer System (requires uninstall)
suite "MyApp.app" Multi-app suites /Applications

Minimal cask (DMG with app)

cask "myapp" do
  version "1.0"
  sha256 "abc123..."

  url "https://example.com/MyApp-#{version}.dmg"
  name "MyApp"
  desc "Does something useful"
  homepage "https://example.com/myapp"

  app "MyApp.app"

  zap trash: [
    "~/Library/Application Support/MyApp",
    "~/Library/Preferences/com.example.myapp.plist",
  ]
end

Font cask

cask "font-my-font" do
  version "2.0"
  sha256 "abc123..."

  url "https://example.com/MyFont-#{version}.zip"
  name "My Font"
  desc "A beautiful typeface"
  homepage "https://example.com/myfont"

  font "MyFont-Regular.ttf"
  font "MyFont-Bold.ttf"
  font "MyFont-Italic.ttf"
end

Arbitrary file placement

cask "mycli" do
  version "1.0"
  sha256 "abc123..."

  url "https://example.com/mycli-#{version}.zip"
  name "MyCLI"
  desc "A command-line tool"
  homepage "https://example.com/mycli"

  binary "mycli"
  artifact "mycli.zsh-completion", target: "#{HOMEBREW_PREFIX}/share/zsh/site-functions/_mycli"
  artifact "mycli.fish", target: "#{HOMEBREW_PREFIX}/share/fish/vendor_completions.d/mycli.fish"
end

PKG installer

cask "myapp" do
  version "1.0"
  sha256 "abc123..."

  url "https://example.com/MyApp-#{version}.pkg"
  name "MyApp"
  desc "Does something useful"
  homepage "https://example.com/myapp"

  pkg "MyApp.pkg"

  uninstall pkgutil: "com.example.myapp"

  zap trash: [
    "~/Library/Application Support/MyApp",
    "~/Library/Preferences/com.example.myapp.plist",
  ]
end

Architecture-conditional cask

cask "myapp" do
  version "1.0"

  on_arm do
    url "https://example.com/MyApp-#{version}-arm64.dmg"
    sha256 "arm_sha..."
  end

  on_intel do
    url "https://example.com/MyApp-#{version}-x86_64.dmg"
    sha256 "intel_sha..."
  end

  name "MyApp"
  desc "Does something useful"
  homepage "https://example.com/myapp"

  app "MyApp.app"
end

Rolling release (no stable version)

cask "myapp" do
  version :latest
  sha256 :no_check

  url "https://example.com/MyApp-latest.dmg"
  name "MyApp"
  desc "Does something useful"
  homepage "https://example.com/myapp"

  auto_updates true   # app has built-in updater
  app "MyApp.app"
end

Bumping a Version

When a new release is out, update three things:

Formula:

url "https://example.com/mytool-1.1.tar.gz"   # new URL
sha256 "newsha256..."                           # recompute (see below)
version "1.1"                                   # if not parseable from url

Cask: same fields, but also verify #{version} interpolation in url still produces the correct download URL with the new version string.

Get the new checksum:

# Option A: download and compute locally
shasum -a 256 mytool-1.1.tar.gz

# Option B: let brew fetch it
brew fetch --force --build-from-source ./Formula/mytool.rb

Then verify:

brew audit mytool
brew test mytool

Faster alternative — let brew do it all:

brew bump-formula-pr mytool --version 1.1 --url https://example.com/mytool-1.1.tar.gz
brew bump-cask-pr myapp --version 1.1

These commands fetch the new tarball, compute the sha256, update the formula/cask, and open a pull request — all in one step. Recommended when submitting bumps to homebrew-core or homebrew-cask.


Submitting to Homebrew Core / Cask

To get your package into the official homebrew/core or homebrew/cask taps (so users can brew install without adding your tap), you need to open a PR against the upstream repo.

Eligibility requirements, the fork/PR workflow, audit flags, commit conventions, and common rejection reasons are covered in full at:

references/homebrew-core-submission.md


Setting up a Personal Tap

1. Create the GitHub repo

Name it homebrew-<tapname> (e.g. homebrew-tools).

2. Directory structure

homebrew-tools/
├── Formula/
│   └── mytool.rb
└── Casks/
    └── myapp.rb

3. Tap and install

brew tap username/tools                      # taps github.com/username/homebrew-tools
brew install username/tools/mytool
brew install --cask username/tools/myapp

4. Test before publishing

brew install --build-from-source ./Formula/mytool.rb
brew install --cask ./Casks/myapp.rb

Development Commands

# Create scaffolds
brew create https://example.com/tool-1.0.tar.gz
brew create --cask https://example.com/App-1.0.dmg

# Edit formula/cask
brew edit mytool
brew edit --cask myapp

# Install for testing
brew install --build-from-source ./Formula/mytool.rb
brew install --cask ./Casks/myapp.rb

# Run tests
brew test mytool
brew test --cask myapp

# Audit (validate before publishing)
brew audit --new mytool
brew audit --new --cask myapp

# Uninstall / reinstall cycle
brew uninstall mytool && brew install --build-from-source ./Formula/mytool.rb

# Style check
brew style ./Formula/mytool.rb
brew style --cask ./Casks/myapp.rb

# Fetch without installing (verify download)
brew fetch mytool
brew fetch --cask myapp

Checksum Workflow

# Compute SHA-256 for a local file
shasum -a 256 myapp-1.0.dmg

# Or let brew fetch and show it
brew fetch --force --build-from-source ./Formula/mytool.rb

brew create auto-populates sha256 if it can download the file during scaffold creation.


Validation

Run before publishing to a tap:

brew audit --new mytool          # stricter checks for new packages
brew audit --new --cask myapp
brew style ./Formula/mytool.rb   # RuboCop style check

Common audit issues:

  • desc must not start with "A" or "The", must not end with a period
  • homepage must use HTTPS
  • license required for formulas (use SPDX identifiers: "MIT", "Apache-2.0", etc.)
  • url must be stable, versioned, and use HTTPS
  • sha256 must match the download exactly
  • Test block required for formulas

References

Weekly Installs
1
First Seen
5 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1