本文为Claude整理编写。

什么是 Homebrew Tap

Homebrew Tap 是第三方的 Homebrew 软件包仓库。通过创建自己的 Tap,你可以发布和分发自己的软件包,而不需要将它们提交到官方的 Homebrew 仓库。

Tap 的命名规则: homebrew-<tapname>

用户使用时的格式:username/tapname/软件名

Formula vs Casks:核心概念

这是 Homebrew 中最重要的区别,理解这一点对于正确组织你的 Tap 至关重要。

Formula(公式)

用途: 安装命令行工具、从源代码编译的软件、开发库和服务

特点:

  • 可以从源代码编译或安装预编译二进制
  • 安装到 /usr/local/bin(Intel)或 /opt/homebrew/bin(Apple Silicon)
  • 在终端中运行
  • 文件放在 Formula/ 目录

安装命令:

brew install git
brew install python
brew install myapp

适用场景:

  • ✅ 命令行工具(git, wget, curl)
  • ✅ 编程语言(python, node, ruby)
  • ✅ 开发库(openssl, libpq)
  • ✅ 后台服务(redis, nginx, postgresql)
  • ✅ 系统工具和实用程序

Casks(桶)

用途: 安装 macOS 原生应用程序、字体、驱动等

特点:

  • 只处理预编译的安装包(.dmg, .pkg, .app, .zip)
  • 应用安装到 /Applications
  • 字体安装到 ~/Library/Fonts
  • 用鼠标点击打开
  • 文件放在 Casks/ 目录

安装命令:

brew install --cask visual-studio-code
brew install --cask google-chrome
brew install --cask font-fira-code

适用场景:

  • ✅ GUI 应用程序(Chrome, Slack, VS Code)
  • ✅ 字体(必须使用 Cask)
  • ✅ 驱动程序
  • ✅ 菜单栏工具
  • ✅ 任何 .dmg 或 .pkg 分发的软件

对比表格

特性 Formula Casks
安装内容 命令行工具、库、服务 GUI 应用、字体、驱动
文件格式 源代码或二进制 .dmg, .pkg, .app, .zip
安装位置 /opt/homebrew/bin /Applications
使用方式 命令行运行 图形界面打开
安装命令 brew install xxx brew install --cask xxx
目录位置 Formula/ Casks/

简单记忆法:

  • Formula = 在终端运行的东西(CLI)
  • Casks = 用鼠标点击的东西(GUI)+ 字体

多 Tap 仓库策略

当你需要发布多个软件包时,有两种主要策略。

策略一:单个 Tap 包含多个软件(推荐)

适用场景:

  • ✅ 5-30 个相关软件包
  • ✅ 所有软件属于同一项目或组织
  • ✅ 希望用户体验简单
  • ✅ 便于统一管理和维护

仓库结构:

homebrew-mycompany/
├── Formula/
│   ├── myapp-cli.rb       # 命令行工具
│   ├── myapp-server.rb    # 后台服务
│   └── myapp-lib.rb       # 开发库
├── Casks/
│   ├── myapp-desktop.rb   # 桌面应用
│   └── myapp-studio.rb    # GUI 工具
└── README.md

用户使用:

# 只需添加一次 Tap
brew tap username/mycompany

# 安装任意软件
brew install myapp-cli
brew install myapp-server
brew install --cask myapp-desktop

优点:

  • 用户体验简单(只需添加一次 Tap)
  • 统一的版本管理和发布周期
  • 软件包之间可以方便地互相依赖
  • 易于维护和更新

缺点:

  • 软件包过多时仓库会变大
  • 不同类型软件混在一起

策略二:多个独立的 Tap 仓库

适用场景:

  • ✅ 发布字体(必须使用 homebrew-fonts
  • ✅ 软件包超过 30 个
  • ✅ 不同类别由不同团队维护
  • ✅ 需要独立的发布周期

多仓库结构:

github.com/username/
├── homebrew-tools/          # 命令行工具
│   └── Formula/
│       ├── cli-tool1.rb
│       └── cli-tool2.rb
├── homebrew-apps/           # GUI 应用
│   └── Casks/
│       ├── desktop-app1.rb
│       └── desktop-app2.rb
└── homebrew-fonts/          # 字体(特殊要求)
    └── Casks/
        ├── font-custom1.rb
        └── font-custom2.rb

用户使用:

# 需要分别添加每个 Tap
brew tap username/tools
brew tap username/apps
brew tap username/fonts

# 从不同 Tap 安装
brew install cli-tool1
brew install --cask desktop-app1
brew install --cask font-custom1

# 或者使用完整路径(自动添加 Tap)
brew install username/tools/cli-tool1

优点:

  • 清晰的分类和组织
  • 可以有独立的维护团队和权限
  • 每个仓库更轻量
  • 符合 Homebrew 约定(字体必须单独)

缺点:

  • 用户需要添加多个 Tap
  • 维护成本更高
  • 跨 Tap 依赖较复杂

选择建议

默认选择单个 Tap,除非:

  1. 你要发布字体(必须使用独立的 homebrew-fonts
  2. 软件包超过 30 个
  3. 有明确的分类需求且由不同团队维护
  4. 需要完全独立的发布周期

创建 Tap 仓库

第一步:在 GitHub 创建仓库

仓库命名规则:

homebrew-<tapname>

常见命名示例:

  • homebrew-tools - 工具集合
  • homebrew-mycompany - 公司产品套件
  • homebrew-fonts - 字体(必须这个名称)
  • homebrew-cli - 命令行工具集

创建步骤:

  1. 在 GitHub 上创建公开仓库
  2. 命名为 homebrew-<你的tap名称>
  3. 添加 README.md 和 LICENSE(推荐 MIT)
  4. 初始化 Git 仓库
mkdir homebrew-mytap
cd homebrew-mytap
git init
echo "# My Homebrew Tap" > README.md
git add README.md
git commit -m "Initial commit"
git remote add origin git@github.com:username/homebrew-mytap.git
git push -u origin main

第二步:创建目录结构

标准结构:

homebrew-mytap/
├── Formula/              # 命令行工具放这里
│   ├── tool1.rb
│   └── tool2.rb
├── Casks/               # GUI 应用和字体放这里
│   ├── app1.rb
│   └── font-custom.rb
├── .github/
│   └── workflows/
│       └── test.yml     # CI/CD 配置(可选)
├── scripts/             # 维护脚本(可选)
│   └── update.sh
├── README.md
└── LICENSE

创建目录:

mkdir -p Formula Casks .github/workflows scripts

编写 Formula

Formula 用于命令行工具和从源代码编译的软件。

基础 Formula 模板

class Myapp < Formula
  desc "简短的软件描述(一句话)"
  homepage "https://github.com/username/myapp"
  url "https://github.com/username/myapp/archive/v1.0.0.tar.gz"
  sha256 "使用 shasum -a 256 计算的哈希值"
  license "MIT"  # 或 "Apache-2.0", "GPL-3.0" 等

  # 依赖项
  depends_on "cmake" => :build      # 构建时依赖
  depends_on "openssl@3"            # 运行时依赖

  def install
    # 编译和安装逻辑
    system "make", "PREFIX=#{prefix}"
    bin.install "myapp"
  end

  test do
    # 测试安装是否成功
    system "#{bin}/myapp", "--version"
  end
end

预编译二进制包

class Myapp < Formula
  desc "预编译的命令行工具"
  homepage "https://github.com/username/myapp"
  url "https://github.com/username/myapp/releases/download/v1.0.0/myapp-1.0.0-darwin.tar.gz"
  sha256 "sha256_hash_here"
  license "MIT"
  version "1.0.0"

  def install
    bin.install "myapp"
    
    # 如果有配置文件
    etc.install "config/myapp.conf"
    
    # 如果有文档
    doc.install "README.md"
    
    # 如果有 man 手册
    man1.install "man/myapp.1"
  end

  test do
    assert_match version.to_s, shell_output("#{bin}/myapp --version")
  end
end

从源代码编译

class Myapp < Formula
  desc "从源代码编译的应用"
  homepage "https://github.com/username/myapp"
  url "https://github.com/username/myapp/archive/v1.0.0.tar.gz"
  sha256 "sha256_here"
  license "MIT"

  depends_on "rust" => :build       # Rust 项目
  # 或
  depends_on "go" => :build         # Go 项目
  # 或
  depends_on "cmake" => :build      # C/C++ 项目

  def install
    # Rust 项目
    system "cargo", "install", "--locked", "--root", prefix, "--path", "."
    
    # 或 Go 项目
    # system "go", "build", "-o", bin/"myapp"
    
    # 或 CMake 项目
    # system "cmake", "-S", ".", "-B", "build", "-DCMAKE_INSTALL_PREFIX=#{prefix}"
    # system "cmake", "--build", "build"
    # system "cmake", "--install", "build"
  end

  test do
    system "#{bin}/myapp", "--help"
  end
end

依赖管理

class Myapp < Formula
  desc "复杂依赖的应用"
  homepage "https://example.com"
  url "https://example.com/myapp-1.0.0.tar.gz"
  sha256 "sha256_here"

  # 构建时依赖(安装后会被删除)
  depends_on "cmake" => :build
  depends_on "pkg-config" => :build

  # 运行时依赖
  depends_on "openssl@3"
  depends_on "postgresql@14"

  # 可选依赖
  depends_on "python@3.11" => :optional

  # 同一 Tap 内的依赖
  depends_on "username/mytap/mylib"

  # 其他 Tap 的依赖
  depends_on "othertap/repo/tool"

  # 系统依赖
  depends_on :macos => :monterey  # 要求 macOS 12+

  def install
    system "./configure", "--prefix=#{prefix}", "--with-openssl=#{Formula["openssl@3"].opt_prefix}"
    system "make", "install"
  end
end

安装后脚本和服务

class Myservice < Formula
  desc "后台服务"
  homepage "https://example.com"
  url "https://example.com/myservice-1.0.0.tar.gz"
  sha256 "sha256_here"

  def install
    bin.install "myservice"
    etc.install "myservice.conf"
  end

  # 定义服务
  service do
    run [opt_bin/"myservice", "--config", etc/"myservice.conf"]
    keep_alive true
    working_dir var
    log_path var/"log/myservice.log"
    error_log_path var/"log/myservice.error.log"
  end

  # 安装后提示
  def caveats
    <<~EOS
      配置文件位于:
        #{etc}/myservice.conf

      启动服务:
        brew services start myservice

      查看日志:
        tail -f #{var}/log/myservice.log
    EOS
  end

  test do
    system "#{bin}/myservice", "--version"
  end
end

编写 Casks

Casks 用于 GUI 应用程序和字体。

GUI 应用程序 Cask

cask "myapp" do
  version "1.0.0"
  sha256 "sha256_of_dmg_or_zip"

  url "https://github.com/username/myapp/releases/download/v#{version}/MyApp-#{version}.dmg"
  name "My Application"
  desc "应用程序的详细描述"
  homepage "https://github.com/username/myapp"

  # 指定支持的 macOS 版本
  depends_on macos: ">= :monterey"

  # 安装应用程序
  app "MyApp.app"

  # 可选:同时安装命令行工具
  binary "#{appdir}/MyApp.app/Contents/MacOS/myapp-cli"

  # 可选:创建符号链接
  # shimscript = "#{staged_path}/myapp.wrapper.sh"
  # binary shimscript, target: "myapp"

  # 卸载前的清理
  uninstall quit: "com.company.myapp",
            delete: "/Library/PrivilegedHelperTools/com.company.myapp.helper"

  # 卸载后的清理
  zap trash: [
    "~/Library/Application Support/MyApp",
    "~/Library/Preferences/com.company.myapp.plist",
    "~/Library/Caches/com.company.myapp",
  ]
end

从 ZIP 安装的应用

cask "myapp" do
  version "2.0.0"
  sha256 "sha256_here"

  url "https://releases.example.com/MyApp-#{version}.zip"
  name "My App"
  desc "应用描述"
  homepage "https://example.com"

  app "MyApp.app"

  # 如果 ZIP 中有子目录
  # app "MyApp-#{version}/MyApp.app"
end

从 PKG 安装的应用

cask "myapp" do
  version "3.0.0"
  sha256 "sha256_here"

  url "https://example.com/MyApp-#{version}.pkg"
  name "My App"
  desc "应用描述"
  homepage "https://example.com"

  pkg "MyApp-#{version}.pkg"

  uninstall pkgutil: "com.company.myapp"
end

字体 Cask

字体包必须放在名为 homebrew-fonts 的 Tap 中。

字体命名规范

文件命名格式:

font-<字体名称>.rb
  • 使用小写字母
  • 单词之间用连字符分隔
  • 必须以 font- 开头

示例:

  • font-fira-code.rb
  • font-source-code-pro.rb
  • font-roboto-mono.rb
  • FiraCode.rb ❌(错误)
  • font_fira_code.rb ❌(错误)

单个字体文件

cask "font-my-custom-font" do
  version "1.0.0"
  sha256 "sha256_here"

  url "https://github.com/username/my-font/releases/download/v#{version}/MyFont.ttf"
  name "My Custom Font"
  desc "一个自定义字体"
  homepage "https://github.com/username/my-font"

  font "MyFont.ttf"
end

字体家族(多个文件)

cask "font-my-font-family" do
  version "2.0.0"
  sha256 "sha256_here"

  url "https://github.com/username/my-fonts/releases/download/v#{version}/MyFontFamily.zip"
  name "My Font Family"
  desc "完整的字体家族"
  homepage "https://github.com/username/my-fonts"

  # 安装所有字重
  font "MyFontFamily-Thin.ttf"
  font "MyFontFamily-Light.ttf"
  font "MyFontFamily-Regular.ttf"
  font "MyFontFamily-Medium.ttf"
  font "MyFontFamily-SemiBold.ttf"
  font "MyFontFamily-Bold.ttf"
  font "MyFontFamily-ExtraBold.ttf"
  font "MyFontFamily-Black.ttf"

  # 如果有斜体
  font "MyFontFamily-Italic.ttf"
  font "MyFontFamily-BoldItalic.ttf"
end

字体在子目录中

cask "font-complete-family" do
  version "3.0.0"
  sha256 "sha256_here"

  url "https://example.com/fonts-#{version}.zip"
  name "Complete Font Family"
  homepage "https://example.com"

  # 字体在 ZIP 的子目录中
  font "fonts/ttf/Regular.ttf"
  font "fonts/ttf/Bold.ttf"
  font "fonts/otf/Regular.otf"
  font "fonts/otf/Bold.otf"

  # 使用通配符(不推荐,显式列出更好)
  # font "fonts/ttf/*.ttf"
end

可变字体

cask "font-variable-font" do
  version "1.5.0"
  sha256 "sha256_here"

  url "https://github.com/username/variable-font/releases/download/v#{version}/VariableFont.ttf"
  name "Variable Font"
  desc "可变字体,支持多个字重和宽度"
  homepage "https://github.com/username/variable-font"

  font "VariableFont-VF.ttf"  # VF = Variable Font
end

多架构和 Universal Binary

macOS 现在有 Intel(x86_64)和 Apple Silicon(ARM64)两种架构,正确处理多架构至关重要。

策略一:分别提供不同架构的包

class Myapp < Formula
  desc "多架构应用"
  homepage "https://github.com/username/myapp"
  version "1.0.0"
  license "MIT"

  on_macos do
    if Hardware::CPU.intel?
      url "https://github.com/username/myapp/releases/download/v1.0.0/myapp-x86_64-darwin.tar.gz"
      sha256 "intel_mac_sha256_here"
    elsif Hardware::CPU.arm?
      url "https://github.com/username/myapp/releases/download/v1.0.0/myapp-aarch64-darwin.tar.gz"
      sha256 "apple_silicon_sha256_here"
    end
  end

  on_linux do
    if Hardware::CPU.intel?
      if Hardware::CPU.is_64_bit?
        url "https://github.com/username/myapp/releases/download/v1.0.0/myapp-x86_64-linux.tar.gz"
        sha256 "linux_x86_64_sha256_here"
      end
    elsif Hardware::CPU.arm?
      if Hardware::CPU.is_64_bit?
        url "https://github.com/username/myapp/releases/download/v1.0.0/myapp-aarch64-linux.tar.gz"
        sha256 "linux_arm64_sha256_here"
      end
    end
  end

  def install
    bin.install "myapp"
  end

  test do
    system "#{bin}/myapp", "--version"
  end
end

策略二:Universal Binary(推荐)

Universal Binary 是包含多个架构的单个二进制文件,用户体验最好。

class Myapp < Formula
  desc "Universal 应用程序"
  homepage "https://github.com/username/myapp"
  url "https://github.com/username/myapp/releases/download/v1.0.0/myapp-universal-darwin.tar.gz"
  sha256 "universal_sha256_here"
  license "MIT"
  version "1.0.0"

  def install
    bin.install "myapp"
  end

  test do
    assert_match version.to_s, shell_output("#{bin}/myapp --version")
    
    # 验证是 Universal Binary
    assert_match "x86_64", shell_output("lipo -info #{bin}/myapp")
    assert_match "arm64", shell_output("lipo -info #{bin}/myapp")
  end
end

如何创建 Universal Binary

如果你在构建自己的软件,可以这样创建 Universal Binary:

# 编译 Intel 版本
cargo build --release --target x86_64-apple-darwin

# 编译 ARM 版本
cargo build --release --target aarch64-apple-darwin

# 合并成 Universal Binary
lipo -create \
  target/x86_64-apple-darwin/release/myapp \
  target/aarch64-apple-darwin/release/myapp \
  -output myapp-universal

# 验证
lipo -info myapp-universal
# 输出: Architectures in the fat file: myapp-universal are: x86_64 arm64

Cask 的多架构处理

对于 GUI 应用:

cask "myapp" do
  version "1.0.0"

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

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

  name "My App"
  desc "应用描述"
  homepage "https://example.com"

  app "MyApp.app"
end

Universal App(Cask)

cask "myapp" do
  version "2.0.0"
  sha256 "universal_dmg_sha256"

  url "https://releases.example.com/MyApp-#{version}-Universal.dmg"
  name "My App"
  desc "Universal 应用(支持 Intel 和 Apple Silicon)"
  homepage "https://example.com"

  app "MyApp.app"
end

Bottle(预编译 Formula)

Bottle 是 Homebrew 的预编译二进制缓存,可以大幅提升安装速度。

class Myapp < Formula
  desc "支持 Bottle 的应用"
  homepage "https://github.com/username/myapp"
  url "https://github.com/username/myapp/archive/v1.0.0.tar.gz"
  sha256 "source_sha256"
  license "MIT"

  bottle do
    root_url "https://github.com/username/homebrew-tap/releases/download/myapp-1.0.0"
    sha256 cellar: :any_skip_relocation, ventura:       "ventura_intel_sha256"
    sha256 cellar: :any_skip_relocation, monterey:      "monterey_intel_sha256"
    sha256 cellar: :any_skip_relocation, arm64_ventura: "ventura_arm_sha256"
    sha256 cellar: :any_skip_relocation, arm64_monterey: "monterey_arm_sha256"
  end

  def install
    bin.install "myapp"
  end
end

管理多个软件包

命名策略

当一个 Tap 包含多个软件时,使用清晰的命名前缀:

Formula/
├── mycompany-cli.rb         # 命令行工具
├── mycompany-server.rb      # 服务器
├── mycompany-lib.rb         # 库
├── mycompany-utils.rb       # 工具集
└── mycompany-admin.rb       # 管理工具

Casks/
├── mycompany-desktop.rb     # 桌面应用
├── mycompany-studio.rb      # 开发工具
└── mycompany-monitor.rb     # 监控工具

软件包之间的依赖

同一 Tap 内的依赖:

# Formula/myapp-core.rb
class MyappCore < Formula
  desc "核心库"
  homepage "https://example.com"
  url "https://example.com/core-1.0.0.tar.gz"
  sha256 "sha256_here"

  def install
    lib.install "libmyapp.a"
    include.install "myapp.h"
  end
end

# Formula/myapp-cli.rb
class MyappCli < Formula
  desc "命令行工具(依赖核心库)"
  homepage "https://example.com"
  url "https://example.com/cli-1.0.0.tar.gz"
  sha256 "sha256_here"

  # 依赖同一 Tap 的其他 formula
  depends_on "username/mytap/myapp-core"

  def install
    bin.install "myapp"
  end
end

# Formula/myapp-server.rb
class MyappServer < Formula
  desc "服务器(依赖核心库)"
  depends_on "username/mytap/myapp-core"
  # ...
end

跨 Tap 的依赖:

class Myapp < Formula
  desc "依赖多个 Tap 的应用"
  
  # 依赖官方 Homebrew
  depends_on "python@3.11"
  
  # 依赖自己的其他 Tap
  depends_on "username/tools/helper"
  
  # 依赖第三方 Tap
  depends_on "otherperson/tap/library"
  
  def install
    bin.install "myapp"
  end
end

元包(Meta Package)

创建一个"全家桶"安装包:

# Formula/mycompany-suite.rb
class MycompanySuite < Formula
  desc "完整安装包(包含所有工具)"
  homepage "https://example.com"
  url "https://example.com/dummy-1.0.0.tar.gz"  # 空包即可
  sha256 "dummy_sha256"
  version "1.0.0"

  depends_on "username/mytap/mycompany-cli"
  depends_on "username/mytap/mycompany-server"
  depends_on "username/mytap/mycompany-lib"

  def install
    # 不需要安装任何东西,只是依赖关系
    (prefix/"README.txt").write <<~EOS
      这是一个元包,已安装以下组件:
      - mycompany-cli
      - mycompany-server
      - mycompany-lib
    EOS
  end

  def caveats
    <<~EOS
      已安装 MyCompany 完整工具套件!

      使用 `mycompany-cli --help` 查看帮助
    EOS
  end
end

测试和验证

本地测试 Formula

# 测试语法是否正确
brew audit --new Formula/myapp.rb

# 严格模式审计(推荐)
brew audit --strict Formula/myapp.rb

# 本地安装测试
brew install --build-from-source ./Formula/myapp.rb

# 运行 Formula 内置测试
brew test myapp

# 卸载测试
brew uninstall myapp

本地测试 Cask

# 测试语法
brew audit --cask Casks/myapp.rb

# 本地安装
brew install --cask ./Casks/myapp.rb

# 卸载
brew uninstall --cask myapp

计算 SHA256

# 本地文件
shasum -a 256 myapp-1.0.0.tar.gz

# 远程文件
curl -sL https://example.com/myapp.tar.gz | shasum -a 256

发布和更新

版本更新流程

  1. 更新下载 URL 中的版本号
  2. 计算新版本的 SHA256
  3. 更新 Formula/Cask 文件
  4. 提交并推送到 GitHub
# 更新版本的快捷命令
brew bump-formula-pr --url=https://example.com/myapp-2.0.0.tar.gz username/mytap/myapp

GitHub Actions 自动化

创建 .github/workflows/test.yml

name: Homebrew Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Homebrew
        uses: Homebrew/actions/setup-homebrew@master

      - name: Audit Formulas
        run: |
          for formula in Formula/*.rb; do
            brew audit --strict "$formula"
          done          

      - name: Audit Casks
        run: |
          for cask in Casks/*.rb; do
            brew audit --cask "$cask"
          done          

常见问题

SHA256 不匹配

# 重新计算
curl -sL <下载URL> | shasum -a 256

找不到 Formula

确保:

  • 文件名与类名匹配(myapp.rbclass Myapp
  • 文件在正确目录(Formula/Casks/

安装到错误位置

  • CLI 工具用 Formula,安装到 /opt/homebrew/bin
  • GUI 应用用 Cask,安装到 /Applications

完整 README 模板

# Homebrew Tap for MyCompany

## 安装

```bash
brew tap username/mytap

可用软件

命令行工具

名称 描述 安装命令
myapp-cli CLI 工具 brew install myapp-cli

GUI 应用

名称 描述 安装命令
myapp-desktop 桌面应用 brew install --cask myapp-desktop

更新

brew update
brew upgrade myapp-cli

卸载

brew uninstall myapp-cli
brew untap username/mytap

参考资源