本文为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,除非:
- 你要发布字体(必须使用独立的
homebrew-fonts) - 软件包超过 30 个
- 有明确的分类需求且由不同团队维护
- 需要完全独立的发布周期
创建 Tap 仓库
第一步:在 GitHub 创建仓库
仓库命名规则:
homebrew-<tapname>
常见命名示例:
homebrew-tools- 工具集合homebrew-mycompany- 公司产品套件homebrew-fonts- 字体(必须这个名称)homebrew-cli- 命令行工具集
创建步骤:
- 在 GitHub 上创建公开仓库
- 命名为
homebrew-<你的tap名称> - 添加 README.md 和 LICENSE(推荐 MIT)
- 初始化 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
发布和更新
版本更新流程
- 更新下载 URL 中的版本号
- 计算新版本的 SHA256
- 更新 Formula/Cask 文件
- 提交并推送到 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.rb→class 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